use warnings; use 5.028; use Test::More; =head1 SYNOPSIS Tests for F, to be run in the browser. =head1 Author, Copyright, and License B<< WebPerl - L >> Copyright (c) 2018 Hauke Daempfling (haukex@zero-g.net) at the Leibniz Institute of Freshwater Ecology and Inland Fisheries (IGB), Berlin, Germany, L This program is free software; you can redistribute it and/or modify it under the same terms as Perl 5 itself: either the GNU General Public License as published by the Free Software Foundation (either version 1, or, at your option, any later version), or the "Artistic License" which comes with Perl 5. This program is distributed in the hope that it will be useful, but B; without even the implied warranty of B. See the licenses for details. You should have received a copy of the licenses along with this program. If not, see L. =cut # A couple of debugging aides: #$WebPerl::TRACE = 1; #js('Perl.trace = true'); #$Carp::Verbose = 1; # or: #use Devel::StackTrace (); #$SIG{__DIE__} = sub { die join '', shift, map {"\t".$_->as_string."\n"} # Devel::StackTrace->new(skip_frames=>2)->frames }; BEGIN { use_ok('WebPerl', qw/ js encode_json sub1 /) } subtest 'basic js() tests' => sub { is js(undef), undef, 'undef'; is js(q{ undefined }), undef, 'undefined'; is js(q{ true }), !0, 'true'; is js(q{ false }), !1, 'false'; is js(q{ 5 }), 5, 'number 1'; is js(q{ 3.14159 }), 3.14159, 'number 2'; is js(q{ 3.14159e6 }), 3.14159e6, 'number 3'; is js(q{ 3.14159e60 }), 3.14159e60, 'number 4'; is js(q{ "" }), "", 'string 1'; is js(q{ "foo" }), "foo", 'string 2'; is js(qq{"\0\\0\N{U+E4}\\u00E4"}), "\0\0\N{U+E4}\N{U+E4}", 'nuls and unicode'; #" }; subtest 'basic function tests' => sub { my $func = js(q{ (function (x,y) { return "x"+x+"y"+y+"z" }) }); isa_ok $func, 'WebPerl::JSObject'; is $func->("a","b"), "xaybz", 'func call'; is js('(function (d) {return d})')->("\0\N{U+E4}\x{2665}"), "\0\N{U+E4}\x{2665}", 'unicode passed as arg & rv'; my $pl = $func->toperl; is ref $pl, 'CODE', 'toperl gives code ref'; is $pl->('i','j'), 'xiyjz', 'code ref works'; is js( '('.js([qw/foo bar quz/])->jscode.')[1]' ), "bar", 'basic jscode test'; ok js( '('.js('document')->jscode.')===('.js('document')->jscode.')' ), 'jscode equality test'; ok !js( '('.js('document')->jscode.')===('.js('window')->jscode.')' ), 'jscode inequality test'; }; subtest 'basic array test' => sub { my $arr = js(q{ testarray = [3,1,4,1,5,9]; (testarray) }); isa_ok $arr, 'WebPerl::JSObject'; is_deeply [@$arr], [3,1,4,1,5,9], 'array values'; $arr->[4] = 'x'; is js('testarray[4]'), 'x', 'setting successful'; my $ref = $arr->arrayref; is_deeply $ref, [3,1,4,1,'x',9], 'arrayref values'; is ref $ref, 'ARRAY', 'arrayref gives arrayref'; isa_ok tied(@$ref), 'WebPerl::JSObject::TiedArray'; $ref->[5] = 'y'; is js('testarray[5]'), 'y', 'setting successful 2'; my $plain = $arr->toperl; is_deeply $plain, [3,1,4,1,'x','y'], 'toperl values'; is ref $plain, 'ARRAY', 'toperl gives arrayref'; ok !tied(@$plain), 'toperl result isn\'t tied'; }; subtest 'basic object test' => sub { my $obj = js(q{ ({ hello: "world!", foo : function () { return "foobar!" } }) }); isa_ok $obj, 'WebPerl::JSObject'; is_deeply [sort keys %$obj], ['foo','hello'], 'keys on object'; is $obj->{hello}, "world!", 'simple object value'; isa_ok $obj->{foo}, 'WebPerl::JSObject'; is $obj->foo, 'foobar!', 'method call'; }; subtest 'encode_json' => sub { my $json = encode_json( { Hello=>"World!" } ); my $jo = js( "($json)" ); isa_ok $jo, 'WebPerl::JSObject', 'json'; is $jo->{Hello}, 'World!', 'json object key/value'; }; subtest 'advanced function tests' => sub { js(' (function (cb) { cb("yup") }) ') ->( sub1 { is shift, "yup", "calling between JS<->Perl"; } ); my $passthru = js(q{ (function (cb) { var rv = cb({hello:"world"}); return rv } ) }) ->(sub { return shift }); isa_ok $passthru, 'WebPerl::JSObject', 'passthru worked 1'; is $passthru->{hello}, 'world', 'passthru worked 2'; }; done_testing; note "All tests passed!" if Test::More->builder->is_passing; # -*- buffer-read-only: t -*- # !!!!!!! DO NOT EDIT THIS FILE !!!!!!! # This file is built by regen/warnings.pl. # Any changes made here will be lost! package warnings; our $VERSION = "1.42"; # Verify that we're called correctly so that warnings will work. # Can't use Carp, since Carp uses us! # String regexps because constant folding = smaller optree = less memory vs regexp literal # see also strict.pm. die sprintf "Incorrect use of pragma '%s' at %s line %d.\n", __PACKAGE__, +(caller)[1,2] if __FILE__ !~ ( '(?x) \b '.__PACKAGE__.' \.pmc? \z' ) && __FILE__ =~ ( '(?x) \b (?i:'.__PACKAGE__.') \.pmc? \z' ); our %Offsets = ( # Warnings Categories added in Perl 5.008 'all' => 0, 'closure' => 2, 'deprecated' => 4, 'exiting' => 6, 'glob' => 8, 'io' => 10, 'closed' => 12, 'exec' => 14, 'layer' => 16, 'newline' => 18, 'pipe' => 20, 'unopened' => 22, 'misc' => 24, 'numeric' => 26, 'once' => 28, 'overflow' => 30, 'pack' => 32, 'portable' => 34, 'recursion' => 36, 'redefine' => 38, 'regexp' => 40, 'severe' => 42, 'debugging' => 44, 'inplace' => 46, 'internal' => 48, 'malloc' => 50, 'signal' => 52, 'substr' => 54, 'syntax' => 56, 'ambiguous' => 58, 'bareword' => 60, 'digit' => 62, 'parenthesis' => 64, 'precedence' => 66, 'printf' => 68, 'prototype' => 70, 'qw' => 72, 'reserved' => 74, 'semicolon' => 76, 'taint' => 78, 'threads' => 80, 'uninitialized' => 82, 'unpack' => 84, 'untie' => 86, 'utf8' => 88, 'void' => 90, # Warnings Categories added in Perl 5.011 'imprecision' => 92, 'illegalproto' => 94, # Warnings Categories added in Perl 5.013 'non_unicode' => 96, 'nonchar' => 98, 'surrogate' => 100, # Warnings Categories added in Perl 5.017 'experimental' => 102, 'experimental::lexical_subs' => 104, 'experimental::regex_sets' => 106, 'experimental::smartmatch' => 108, # Warnings Categories added in Perl 5.019 'experimental::postderef' => 110, 'experimental::signatures' => 112, 'syscalls' => 114, # Warnings Categories added in Perl 5.021 'experimental::bitwise' => 116, 'experimental::const_attr' => 118, 'experimental::re_strict' => 120, 'experimental::refaliasing' => 122, 'experimental::win32_perlio' => 124, 'locale' => 126, 'missing' => 128, 'redundant' => 130, # Warnings Categories added in Perl 5.025 'experimental::declared_refs' => 132, # Warnings Categories added in Perl 5.027 'experimental::alpha_assertions' => 134, 'experimental::script_run' => 136, 'shadow' => 138, ); our %Bits = ( 'all' => "\x55\x55\x55\x55\x55\x55\x55\x55\x55\x55\x55\x55\x55\x55\x55\x55\x55\x55", # [0..71] 'ambiguous' => "\x00\x00\x00\x00\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", # [29] 'bareword' => "\x00\x00\x00\x00\x00\x00\x00\x10\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", # [30] 'closed' => "\x00\x10\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", # [6] 'closure' => "\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", # [1] 'debugging' => "\x00\x00\x00\x00\x00\x10\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", # [22] 'deprecated' => "\x10\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", # [2] 'digit' => "\x00\x00\x00\x00\x00\x00\x00\x40\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", # [31] 'exec' => "\x00\x40\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", # [7] 'exiting' => "\x40\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", # [3] 'experimental' => "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x40\x55\x51\x15\x50\x01", # [51..56,58..62,66..68] 'experimental::alpha_assertions' => "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x40\x00", # [67] 'experimental::bitwise' => "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x10\x00\x00\x00", # [58] 'experimental::const_attr' => "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x40\x00\x00\x00", # [59] 'experimental::declared_refs' => "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x10\x00", # [66] 'experimental::lexical_subs' => "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00", # [52] 'experimental::postderef' => "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x40\x00\x00\x00\x00", # [55] 'experimental::re_strict' => "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00", # [60] 'experimental::refaliasing' => "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x04\x00\x00", # [61] 'experimental::regex_sets' => "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x04\x00\x00\x00\x00", # [53] 'experimental::script_run' => "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01", # [68] 'experimental::signatures' => "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00", # [56] 'experimental::smartmatch' => "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x10\x00\x00\x00\x00", # [54] 'experimental::win32_perlio' => "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x10\x00\x00", # [62] 'glob' => "\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", # [4] 'illegalproto' => "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x40\x00\x00\x00\x00\x00\x00", # [47] 'imprecision' => "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x10\x00\x00\x00\x00\x00\x00", # [46] 'inplace' => "\x00\x00\x00\x00\x00\x40\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", # [23] 'internal' => "\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", # [24] 'io' => "\x00\x54\x55\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x04\x00\x00\x00", # [5..11,57] 'layer' => "\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", # [8] 'locale' => "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x40\x00\x00", # [63] 'malloc' => "\x00\x00\x00\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", # [25] 'misc' => "\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", # [12] 'missing' => "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00", # [64] 'newline' => "\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", # [9] 'non_unicode' => "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00", # [48] 'nonchar' => "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x04\x00\x00\x00\x00\x00", # [49] 'numeric' => "\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", # [13] 'once' => "\x00\x00\x00\x10\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", # [14] 'overflow' => "\x00\x00\x00\x40\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", # [15] 'pack' => "\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", # [16] 'parenthesis' => "\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00", # [32] 'pipe' => "\x00\x00\x10\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", # [10] 'portable' => "\x00\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", # [17] 'precedence' => "\x00\x00\x00\x00\x00\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00", # [33] 'printf' => "\x00\x00\x00\x00\x00\x00\x00\x00\x10\x00\x00\x00\x00\x00\x00\x00\x00\x00", # [34] 'prototype' => "\x00\x00\x00\x00\x00\x00\x00\x00\x40\x00\x00\x00\x00\x00\x00\x00\x00\x00", # [35] 'qw' => "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00", # [36] 'recursion' => "\x00\x00\x00\x00\x10\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", # [18] 'redefine' => "\x00\x00\x00\x00\x40\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", # [19] 'redundant' => "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x04\x00", # [65] 'regexp' => "\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", # [20] 'reserved' => "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00\x00", # [37] 'semicolon' => "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x10\x00\x00\x00\x00\x00\x00\x00\x00", # [38] 'severe' => "\x00\x00\x00\x00\x00\x54\x05\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", # [21..25] 'shadow' => "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x04", # [69] 'signal' => "\x00\x00\x00\x00\x00\x00\x10\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", # [26] 'substr' => "\x00\x00\x00\x00\x00\x00\x40\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", # [27] 'surrogate' => "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x10\x00\x00\x00\x00\x00", # [50] 'syntax' => "\x00\x00\x00\x00\x00\x00\x00\x55\x55\x15\x00\x40\x00\x00\x00\x00\x00\x00", # [28..38,47] 'syscalls' => "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x04\x00\x00\x00", # [57] 'taint' => "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x40\x00\x00\x00\x00\x00\x00\x00\x00", # [39] 'threads' => "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00", # [40] 'uninitialized' => "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00", # [41] 'unopened' => "\x00\x00\x40\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", # [11] 'unpack' => "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x10\x00\x00\x00\x00\x00\x00\x00", # [42] 'untie' => "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x40\x00\x00\x00\x00\x00\x00\x00", # [43] 'utf8' => "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x15\x00\x00\x00\x00\x00", # [44,48..50] 'void' => "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00", # [45] ); our %DeadBits = ( 'all' => "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa", # [0..71] 'ambiguous' => "\x00\x00\x00\x00\x00\x00\x00\x08\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", # [29] 'bareword' => "\x00\x00\x00\x00\x00\x00\x00\x20\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", # [30] 'closed' => "\x00\x20\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", # [6] 'closure' => "\x08\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", # [1] 'debugging' => "\x00\x00\x00\x00\x00\x20\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", # [22] 'deprecated' => "\x20\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", # [2] 'digit' => "\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", # [31] 'exec' => "\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", # [7] 'exiting' => "\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", # [3] 'experimental' => "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\xaa\xa2\x2a\xa0\x02", # [51..56,58..62,66..68] 'experimental::alpha_assertions' => "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00", # [67] 'experimental::bitwise' => "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x20\x00\x00\x00", # [58] 'experimental::const_attr' => "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00", # [59] 'experimental::declared_refs' => "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x20\x00", # [66] 'experimental::lexical_subs' => "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x00", # [52] 'experimental::postderef' => "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00", # [55] 'experimental::re_strict' => "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00", # [60] 'experimental::refaliasing' => "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x08\x00\x00", # [61] 'experimental::regex_sets' => "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x08\x00\x00\x00\x00", # [53] 'experimental::script_run' => "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02", # [68] 'experimental::signatures' => "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00", # [56] 'experimental::smartmatch' => "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x20\x00\x00\x00\x00", # [54] 'experimental::win32_perlio' => "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x20\x00\x00", # [62] 'glob' => "\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", # [4] 'illegalproto' => "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00", # [47] 'imprecision' => "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x20\x00\x00\x00\x00\x00\x00", # [46] 'inplace' => "\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", # [23] 'internal' => "\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", # [24] 'io' => "\x00\xa8\xaa\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x08\x00\x00\x00", # [5..11,57] 'layer' => "\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", # [8] 'locale' => "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00", # [63] 'malloc' => "\x00\x00\x00\x00\x00\x00\x08\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", # [25] 'misc' => "\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", # [12] 'missing' => "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00", # [64] 'newline' => "\x00\x00\x08\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", # [9] 'non_unicode' => "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x00\x00", # [48] 'nonchar' => "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x08\x00\x00\x00\x00\x00", # [49] 'numeric' => "\x00\x00\x00\x08\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", # [13] 'once' => "\x00\x00\x00\x20\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", # [14] 'overflow' => "\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", # [15] 'pack' => "\x00\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", # [16] 'parenthesis' => "\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00", # [32] 'pipe' => "\x00\x00\x20\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", # [10] 'portable' => "\x00\x00\x00\x00\x08\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", # [17] 'precedence' => "\x00\x00\x00\x00\x00\x00\x00\x00\x08\x00\x00\x00\x00\x00\x00\x00\x00\x00", # [33] 'printf' => "\x00\x00\x00\x00\x00\x00\x00\x00\x20\x00\x00\x00\x00\x00\x00\x00\x00\x00", # [34] 'prototype' => "\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00", # [35] 'qw' => "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00", # [36] 'recursion' => "\x00\x00\x00\x00\x20\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", # [18] 'redefine' => "\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", # [19] 'redundant' => "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x08\x00", # [65] 'regexp' => "\x00\x00\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", # [20] 'reserved' => "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x08\x00\x00\x00\x00\x00\x00\x00\x00", # [37] 'semicolon' => "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x20\x00\x00\x00\x00\x00\x00\x00\x00", # [38] 'severe' => "\x00\x00\x00\x00\x00\xa8\x0a\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", # [21..25] 'shadow' => "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x08", # [69] 'signal' => "\x00\x00\x00\x00\x00\x00\x20\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", # [26] 'substr' => "\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", # [27] 'surrogate' => "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x20\x00\x00\x00\x00\x00", # [50] 'syntax' => "\x00\x00\x00\x00\x00\x00\x00\xaa\xaa\x2a\x00\x80\x00\x00\x00\x00\x00\x00", # [28..38,47] 'syscalls' => "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x08\x00\x00\x00", # [57] 'taint' => "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00", # [39] 'threads' => "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00", # [40] 'uninitialized' => "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x08\x00\x00\x00\x00\x00\x00\x00", # [41] 'unopened' => "\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", # [11] 'unpack' => "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x20\x00\x00\x00\x00\x00\x00\x00", # [42] 'untie' => "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00", # [43] 'utf8' => "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x2a\x00\x00\x00\x00\x00", # [44,48..50] 'void' => "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x08\x00\x00\x00\x00\x00\x00", # [45] ); # These are used by various things, including our own tests our $NONE = "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"; our $DEFAULT = "\x10\x01\x00\x00\x00\x50\x04\x00\x00\x00\x00\x00\x00\x55\x51\x55\x50\x01", # [2,4,22,23,25,52..56,58..63,66..68] our $LAST_BIT = 140 ; our $BYTES = 18 ; sub Croaker { require Carp; # this initializes %CarpInternal local $Carp::CarpInternal{'warnings'}; delete $Carp::CarpInternal{'warnings'}; Carp::croak(@_); } sub _expand_bits { my $bits = shift; my $want_len = ($LAST_BIT + 7) >> 3; my $len = length($bits); if ($len != $want_len) { if ($bits eq "") { $bits = "\x00" x $want_len; } elsif ($len > $want_len) { substr $bits, $want_len, $len-$want_len, ""; } else { my $a = vec($bits, $Offsets{all} >> 1, 2); $a |= $a << 2; $a |= $a << 4; $bits .= chr($a) x ($want_len - $len); } } return $bits; } sub _bits { my $mask = shift ; my $catmask ; my $fatal = 0 ; my $no_fatal = 0 ; $mask = _expand_bits($mask); foreach my $word ( @_ ) { if ($word eq 'FATAL') { $fatal = 1; $no_fatal = 0; } elsif ($word eq 'NONFATAL') { $fatal = 0; $no_fatal = 1; } elsif ($catmask = $Bits{$word}) { $mask |= $catmask ; $mask |= $DeadBits{$word} if $fatal ; $mask = ~(~$mask | $DeadBits{$word}) if $no_fatal ; } else { Croaker("Unknown warnings category '$word'")} } return $mask ; } sub bits { # called from B::Deparse.pm push @_, 'all' unless @_ ; return _bits("", @_) ; } sub import { shift; my $mask = ${^WARNING_BITS} // ($^W ? $Bits{all} : $DEFAULT) ; # append 'all' when implied (empty import list or after a lone # "FATAL" or "NONFATAL") push @_, 'all' if !@_ || (@_==1 && ($_[0] eq 'FATAL' || $_[0] eq 'NONFATAL')); ${^WARNING_BITS} = _bits($mask, @_); } sub unimport { shift; my $catmask ; my $mask = ${^WARNING_BITS} // ($^W ? $Bits{all} : $DEFAULT) ; # append 'all' when implied (empty import list or after a lone "FATAL") push @_, 'all' if !@_ || @_==1 && $_[0] eq 'FATAL'; $mask = _expand_bits($mask); foreach my $word ( @_ ) { if ($word eq 'FATAL') { next; } elsif ($catmask = $Bits{$word}) { $mask = ~(~$mask | $catmask | $DeadBits{$word}); } else { Croaker("Unknown warnings category '$word'")} } ${^WARNING_BITS} = $mask ; } my %builtin_type; @builtin_type{qw(SCALAR ARRAY HASH CODE REF GLOB LVALUE Regexp)} = (); sub LEVEL () { 8 }; sub MESSAGE () { 4 }; sub FATAL () { 2 }; sub NORMAL () { 1 }; sub __chk { my $category ; my $offset ; my $isobj = 0 ; my $wanted = shift; my $has_message = $wanted & MESSAGE; my $has_level = $wanted & LEVEL ; if ($has_level) { if (@_ != ($has_message ? 3 : 2)) { my $sub = (caller 1)[3]; my $syntax = $has_message ? "category, level, 'message'" : 'category, level'; Croaker("Usage: $sub($syntax)"); } } elsif (not @_ == 1 || @_ == ($has_message ? 2 : 0)) { my $sub = (caller 1)[3]; my $syntax = $has_message ? "[category,] 'message'" : '[category]'; Croaker("Usage: $sub($syntax)"); } my $message = pop if $has_message; if (@_) { # check the category supplied. $category = shift ; if (my $type = ref $category) { Croaker("not an object") if exists $builtin_type{$type}; $category = $type; $isobj = 1 ; } $offset = $Offsets{$category}; Croaker("Unknown warnings category '$category'") unless defined $offset; } else { $category = (caller(1))[0] ; $offset = $Offsets{$category}; Croaker("package '$category' not registered for warnings") unless defined $offset ; } my $i; if ($isobj) { my $pkg; $i = 2; while (do { { package DB; $pkg = (caller($i++))[0] } } ) { last unless @DB::args && $DB::args[0] =~ /^$category=/ ; } $i -= 2 ; } elsif ($has_level) { $i = 2 + shift; } else { $i = _error_loc(); # see where Carp will allocate the error } # Default to 0 if caller returns nothing. Default to $DEFAULT if it # explicitly returns undef. my(@callers_bitmask) = (caller($i))[9] ; my $callers_bitmask = @callers_bitmask ? $callers_bitmask[0] // $DEFAULT : 0 ; length($callers_bitmask) > ($offset >> 3) or $offset = $Offsets{all}; my @results; foreach my $type (FATAL, NORMAL) { next unless $wanted & $type; push @results, vec($callers_bitmask, $offset + $type - 1, 1); } # &enabled and &fatal_enabled return $results[0] unless $has_message; # &warnif, and the category is neither enabled as warning nor as fatal return if ($wanted & (NORMAL | FATAL | MESSAGE)) == (NORMAL | FATAL | MESSAGE) && !($results[0] || $results[1]); # If we have an explicit level, bypass Carp. if ($has_level and @callers_bitmask) { # logic copied from util.c:mess_sv my $stuff = " at " . join " line ", (caller $i)[1,2]; $stuff .= sprintf ", <%s> %s %d", *${^LAST_FH}{NAME}, ($/ eq "\n" ? "line" : "chunk"), $. if $. && ${^LAST_FH}; die "$message$stuff.\n" if $results[0]; return warn "$message$stuff.\n"; } require Carp; Carp::croak($message) if $results[0]; # will always get here for &warn. will only get here for &warnif if the # category is enabled Carp::carp($message); } sub _mkMask { my ($bit) = @_; my $mask = ""; vec($mask, $bit, 1) = 1; return $mask; } sub register_categories { my @names = @_; for my $name (@names) { if (! defined $Bits{$name}) { $Offsets{$name} = $LAST_BIT; $Bits{$name} = _mkMask($LAST_BIT++); $DeadBits{$name} = _mkMask($LAST_BIT++); if (length($Bits{$name}) > length($Bits{all})) { $Bits{all} .= "\x55"; $DeadBits{all} .= "\xaa"; } } } } sub _error_loc { require Carp; goto &Carp::short_error_loc; # don't introduce another stack frame } sub enabled { return __chk(NORMAL, @_); } sub fatal_enabled { return __chk(FATAL, @_); } sub warn { return __chk(FATAL | MESSAGE, @_); } sub warnif { return __chk(NORMAL | FATAL | MESSAGE, @_); } sub enabled_at_level { return __chk(NORMAL | LEVEL, @_); } sub fatal_enabled_at_level { return __chk(FATAL | LEVEL, @_); } sub warn_at_level { return __chk(FATAL | MESSAGE | LEVEL, @_); } sub warnif_at_level { return __chk(NORMAL | FATAL | MESSAGE | LEVEL, @_); } # These are not part of any public interface, so we can delete them to save # space. delete @warnings::{qw(NORMAL FATAL MESSAGE LEVEL)}; 1; __END__ # ex: set ro: package DBM_Filter ; use strict; use warnings; our $VERSION = '0.06'; package Tie::Hash ; use strict; use warnings; use Carp; our %LayerStack = (); our %origDESTROY = (); our %Filters = map { $_, undef } qw( Fetch_Key Fetch_Value Store_Key Store_Value ); our %Options = map { $_, 1 } qw( fetch store ); #sub Filter_Enable #{ #} # #sub Filter_Disable #{ #} sub Filtered { my $this = shift; return defined $LayerStack{$this} ; } sub Filter_Pop { my $this = shift; my $stack = $LayerStack{$this} || return undef ; my $filter = pop @{ $stack }; # remove the filter hooks if this is the last filter to pop if ( @{ $stack } == 0 ) { $this->filter_store_key ( undef ); $this->filter_store_value( undef ); $this->filter_fetch_key ( undef ); $this->filter_fetch_value( undef ); delete $LayerStack{$this}; } return $filter; } sub Filter_Key_Push { &_do_Filter_Push; } sub Filter_Value_Push { &_do_Filter_Push; } sub Filter_Push { &_do_Filter_Push; } sub _do_Filter_Push { my $this = shift; my %callbacks = (); my $caller = (caller(1))[3]; $caller =~ s/^.*:://; croak "$caller: no parameters present" unless @_ ; if ( ! $Options{lc $_[0]} ) { my $class = shift; my @params = @_; # if $class already contains "::", don't prefix "DBM_Filter::" $class = "DBM_Filter::$class" unless $class =~ /::/; no strict 'refs'; # does the "DBM_Filter::$class" exist? if ( ! %{ "${class}::"} ) { # Nope, so try to load it. eval " require $class ; " ; croak "$caller: Cannot Load DBM Filter '$class': $@" if $@; } my $fetch = *{ "${class}::Fetch" }{CODE}; my $store = *{ "${class}::Store" }{CODE}; my $filter = *{ "${class}::Filter" }{CODE}; use strict 'refs'; my $count = defined($filter) + defined($store) + defined($fetch) ; if ( $count == 0 ) { croak "$caller: No methods (Filter, Fetch or Store) found in class '$class'" } elsif ( $count == 1 && ! defined $filter) { my $need = defined($fetch) ? 'Store' : 'Fetch'; croak "$caller: Missing method '$need' in class '$class'" ; } elsif ( $count >= 2 && defined $filter) { croak "$caller: Can't mix Filter with Store and Fetch in class '$class'" } if (defined $filter) { my $callbacks = &{ $filter }(@params); croak "$caller: '${class}::Filter' did not return a hash reference" unless ref $callbacks && ref $callbacks eq 'HASH'; %callbacks = %{ $callbacks } ; } else { $callbacks{Fetch} = $fetch; $callbacks{Store} = $store; } } else { croak "$caller: not even params" unless @_ % 2 == 0; %callbacks = @_; } my %filters = %Filters ; my @got = (); while (my ($k, $v) = each %callbacks ) { my $key = $k; $k = lc $k; if ($k eq 'fetch') { push @got, 'Fetch'; if ($caller eq 'Filter_Push') { $filters{Fetch_Key} = $filters{Fetch_Value} = $v } elsif ($caller eq 'Filter_Key_Push') { $filters{Fetch_Key} = $v } elsif ($caller eq 'Filter_Value_Push') { $filters{Fetch_Value} = $v } } elsif ($k eq 'store') { push @got, 'Store'; if ($caller eq 'Filter_Push') { $filters{Store_Key} = $filters{Store_Value} = $v } elsif ($caller eq 'Filter_Key_Push') { $filters{Store_Key} = $v } elsif ($caller eq 'Filter_Value_Push') { $filters{Store_Value} = $v } } else { croak "$caller: Unknown key '$key'" } croak "$caller: value associated with key '$key' is not a code reference" unless ref $v && ref $v eq 'CODE'; } if ( @got != 2 ) { push @got, 'neither' if @got == 0 ; croak "$caller: expected both Store & Fetch - got @got"; } # remember the class push @{ $LayerStack{$this} }, \%filters ; my $str_this = "$this" ; # Avoid a closure with $this in the subs below $this->filter_store_key ( sub { store_hook($str_this, 'Store_Key') }); $this->filter_store_value( sub { store_hook($str_this, 'Store_Value') }); $this->filter_fetch_key ( sub { fetch_hook($str_this, 'Fetch_Key') }); $this->filter_fetch_value( sub { fetch_hook($str_this, 'Fetch_Value') }); # Hijack the callers DESTROY method $this =~ /^(.*)=/; my $type = $1 ; no strict 'refs'; if ( *{ "${type}::DESTROY" }{CODE} ne \&MyDESTROY ) { $origDESTROY{$type} = *{ "${type}::DESTROY" }{CODE}; no warnings 'redefine'; *{ "${type}::DESTROY" } = \&MyDESTROY ; } } sub store_hook { my $this = shift ; my $type = shift ; foreach my $layer (@{ $LayerStack{$this} }) { &{ $layer->{$type} }() if defined $layer->{$type} ; } } sub fetch_hook { my $this = shift ; my $type = shift ; foreach my $layer (reverse @{ $LayerStack{$this} }) { &{ $layer->{$type} }() if defined $layer->{$type} ; } } sub MyDESTROY { my $this = shift ; delete $LayerStack{$this} ; # call real DESTROY $this =~ /^(.*)=/; &{ $origDESTROY{$1} }($this); } 1; __END__ # Generated from XSLoader_pm.PL (resolved %Config::Config value) # This file is unique for every OS package XSLoader; $VERSION = "0.30"; # remember to update version in POD! #use strict; package DynaLoader; # No prizes for guessing why we don't say 'bootstrap DynaLoader;' here. # NOTE: All dl_*.xs (including dl_none.xs) define a dl_error() XSUB boot_DynaLoader('DynaLoader') if defined(&boot_DynaLoader) && !defined(&dl_error); package XSLoader; sub load { package DynaLoader; my ($caller, $modlibname) = caller(); my $module = $caller; if (@_) { $module = $_[0]; } else { $_[0] = $module; } # work with static linking too my $boots = "$module\::bootstrap"; goto &$boots if defined &$boots; goto \&XSLoader::bootstrap_inherit unless $module and defined &dl_load_file; my @modparts = split(/::/,$module); my $modfname = $modparts[-1]; my $modfname_orig = $modfname; # For .bs file search # Some systems have restrictions on files names for DLL's etc. # mod2fname returns appropriate file base name (typically truncated) # It may also edit @modparts if required. $modfname = &DynaLoader::mod2fname(\@modparts) if defined &DynaLoader::mod2fname; my $modpname = join('/',@modparts); my $c = () = split(/::/,$caller,-1); $modlibname =~ s,[\\/][^\\/]+$,, while $c--; # Q&D basename # Does this look like a relative path? if ($modlibname !~ m{^/}) { # Someone may have a #line directive that changes the file name, or # may be calling XSLoader::load from inside a string eval. We cer- # tainly do not want to go loading some code that is not in @INC, # as it could be untrusted. # # We could just fall back to DynaLoader here, but then the rest of # this function would go untested in the perl core, since all @INC # paths are relative during testing. That would be a time bomb # waiting to happen, since bugs could be introduced into the code. # # So look through @INC to see if $modlibname is in it. A rela- # tive $modlibname is not a common occurrence, so this block is # not hot code. FOUND: { for (@INC) { if ($_ eq $modlibname) { last FOUND; } } # Not found. Fall back to DynaLoader. goto \&XSLoader::bootstrap_inherit; } } my $file = "$modlibname/auto/$modpname/$modfname.none"; # print STDERR "XSLoader::load for $module ($file)\n" if $dl_debug; # N.B. The .bs file does not following the naming convention used # by mod2fname, so use the unedited version of the name. my $bs = "$modlibname/auto/$modpname/$modfname_orig.bs"; # This calls DynaLoader::bootstrap, which will load the .bs file if present goto \&XSLoader::bootstrap_inherit if not -f $file or -s $bs; my $bootname = "boot_$module"; $bootname =~ s/\W/_/g; @DynaLoader::dl_require_symbols = ($bootname); my $boot_symbol_ref; # Many dynamic extension loading problems will appear to come from # this section of code: XYZ failed at line 123 of DynaLoader.pm. # Often these errors are actually occurring in the initialisation # C code of the extension XS file. Perl reports the error as being # in this perl code simply because this was the last perl code # it executed. my $libref = dl_load_file($file, 0) or do { require Carp; Carp::croak("Can't load '$file' for module $module: " . dl_error()); }; push(@DynaLoader::dl_librefs,$libref); # record loaded object $boot_symbol_ref = dl_find_symbol($libref, $bootname) or do { require Carp; Carp::croak("Can't find '$bootname' symbol in $file\n"); }; push(@DynaLoader::dl_modules, $module); # record loaded module boot: my $xs = dl_install_xsub($boots, $boot_symbol_ref, $file); # See comment block above push(@DynaLoader::dl_shared_objects, $file); # record files loaded return &$xs(@_); } sub bootstrap_inherit { require DynaLoader; goto \&DynaLoader::bootstrap_inherit; } 1; __END__ package bigrat; use 5.010; use strict; use warnings; our $VERSION = '0.49'; use Exporter; our @ISA = qw( bigint ); our @EXPORT_OK = qw( PI e bpi bexp hex oct ); our @EXPORT = qw( inf NaN ); use overload; use bigint (); ############################################################################## BEGIN { *inf = \&bigint::inf; *NaN = \&bigint::NaN; *hex = \&bigint::hex; *oct = \&bigint::oct; } # These are all alike, and thus faked by AUTOLOAD my @faked = qw/round_mode accuracy precision div_scale/; our ($AUTOLOAD, $_lite); # _lite for testsuite sub AUTOLOAD { my $name = $AUTOLOAD; $name =~ s/.*:://; # split package no strict 'refs'; foreach my $n (@faked) { if ($n eq $name) { *{"bigrat::$name"} = sub { my $self = shift; no strict 'refs'; if (defined $_[0]) { Math::BigInt->$name($_[0]); Math::BigFloat->$name($_[0]); return Math::BigRat->$name($_[0]); } return Math::BigInt->$name(); }; return &$name; } } # delayed load of Carp and avoid recursion require Carp; Carp::croak ("Can't call bigrat\-\>$name, not a valid method"); } sub unimport { $^H{bigrat} = undef; # no longer in effect overload::remove_constant('binary', '', 'float', '', 'integer'); } sub in_effect { my $level = shift || 0; my $hinthash = (caller($level))[10]; $hinthash->{bigrat}; } ############################################################################# sub import { my $self = shift; # see also bignum->import() for additional comments $^H{bigrat} = 1; # we are in effect # for newer Perls always override hex() and oct() with a lexical version: if ($] > 5.009004) { bigint::_override(); } # some defaults my $lib = ''; my $lib_kind = 'try'; my $upgrade = 'Math::BigFloat'; my @import = (':constant'); # drive it w/ constant my @a = @_; my $l = scalar @_; my $j = 0; my ($a, $p); my ($ver, $trace); # version? trace? for (my $i = 0; $i < $l ; $i++, $j++) { if ($_[$i] eq 'upgrade') { # this causes upgrading $upgrade = $_[$i + 1]; # or undef to disable my $s = 2; $s = 1 if @a - $j < 2; # avoid "can not modify non-existent..." splice @a, $j, $s; $j -= $s; } elsif ($_[$i] =~ /^(l|lib|try|only)$/) { # this causes a different low lib to take care... $lib_kind = $1; $lib_kind = 'lib' if $lib_kind eq 'l'; $lib = $_[$i + 1] || ''; my $s = 2; $s = 1 if @a - $j < 2; # avoid "can not modify non-existent..." splice @a, $j, $s; $j -= $s; $i++; } elsif ($_[$i] =~ /^(a|accuracy)$/) { $a = $_[$i + 1]; my $s = 2; $s = 1 if @a - $j < 2; # avoid "can not modify non-existent..." splice @a, $j, $s; $j -= $s; $i++; } elsif ($_[$i] =~ /^(p|precision)$/) { $p = $_[$i + 1]; my $s = 2; $s = 1 if @a - $j < 2; # avoid "can not modify non-existent..." splice @a, $j, $s; $j -= $s; $i++; } elsif ($_[$i] =~ /^(v|version)$/) { $ver = 1; splice @a, $j, 1; $j--; } elsif ($_[$i] =~ /^(t|trace)$/) { $trace = 1; splice @a, $j, 1; $j--; } elsif ($_[$i] !~ /^(PI|e|bpi|bexp|hex|oct)\z/) { die ("unknown option $_[$i]"); } } my $class; $_lite = 0; # using M::BI::L ? if ($trace) { require Math::BigInt::Trace; $class = 'Math::BigInt::Trace'; $upgrade = 'Math::BigFloat::Trace'; } else { # see if we can find Math::BigInt::Lite if (!defined $a && !defined $p) { # rounding won't work to well local @INC = @INC; pop @INC if $INC[-1] eq '.'; if (eval { require Math::BigInt::Lite; 1 }) { @import = (); # :constant in Lite, not MBI Math::BigInt::Lite->import(':constant'); $_lite = 1; # signal okay } } require Math::BigInt if $_lite == 0; # not already loaded? $class = 'Math::BigInt'; # regardless of MBIL or not } push @import, $lib_kind => $lib if $lib ne ''; # Math::BigInt::Trace or plain Math::BigInt $class->import(@import, upgrade => $upgrade); require Math::BigFloat; Math::BigFloat->import(upgrade => 'Math::BigRat', ':constant'); require Math::BigRat; Math::BigRat->import(@import); bigrat->accuracy($a) if defined $a; bigrat->precision($p) if defined $p; if ($ver) { print "bigrat\t\t\t v$VERSION\n"; print "Math::BigInt::Lite\t v$Math::BigInt::Lite::VERSION\n" if $_lite; print "Math::BigInt\t\t v$Math::BigInt::VERSION"; my $config = Math::BigInt->config(); print " lib => $config->{lib} v$config->{lib_version}\n"; print "Math::BigFloat\t\t v$Math::BigFloat::VERSION\n"; print "Math::BigRat\t\t v$Math::BigRat::VERSION\n"; exit; } # Take care of octal/hexadecimal constants overload::constant binary => sub { bigint::_binary_constant(shift); }; # if another big* was already loaded: my ($package) = caller(); no strict 'refs'; if (!defined *{"${package}::inf"}) { $self->export_to_level(1, $self, @a); # export inf and NaN } } sub PI () { Math::BigFloat->new('3.141592653589793238462643383279502884197'); } sub e () { Math::BigFloat->new('2.718281828459045235360287471352662497757'); } sub bpi ($) { local $Math::BigFloat::upgrade; Math::BigFloat->bpi(@_); } sub bexp ($$) { local $Math::BigFloat::upgrade; my $x = Math::BigFloat->new($_[0]); $x->bexp($_[1]); } 1; __END__ package Fatal; # ABSTRACT: Replace functions with equivalents which succeed or die use 5.008; # 5.8.x needed for autodie use Carp; use strict; use warnings; use Tie::RefHash; # To cache subroutine refs use Config; use Scalar::Util qw(set_prototype); use autodie::Util qw( fill_protos install_subs make_core_trampoline on_end_of_compile_scope ); use constant PERL510 => ( $] >= 5.010 ); use constant LEXICAL_TAG => q{:lexical}; use constant VOID_TAG => q{:void}; use constant INSIST_TAG => q{!}; # Keys for %Cached_fatalised_sub (used in 3rd level) use constant CACHE_AUTODIE_LEAK_GUARD => 0; use constant CACHE_FATAL_WRAPPER => 1; use constant CACHE_FATAL_VOID => 2; use constant ERROR_NOARGS => 'Cannot use lexical %s with no arguments'; use constant ERROR_VOID_LEX => VOID_TAG.' cannot be used with lexical scope'; use constant ERROR_LEX_FIRST => LEXICAL_TAG.' must be used as first argument'; use constant ERROR_NO_LEX => "no %s can only start with ".LEXICAL_TAG; use constant ERROR_BADNAME => "Bad subroutine name for %s: %s"; use constant ERROR_NOTSUB => "%s is not a Perl subroutine"; use constant ERROR_NOT_BUILT => "%s is neither a builtin, nor a Perl subroutine"; use constant ERROR_NOHINTS => "No user hints defined for %s"; use constant ERROR_CANT_OVERRIDE => "Cannot make the non-overridable builtin %s fatal"; use constant ERROR_NO_IPC_SYS_SIMPLE => "IPC::System::Simple required for Fatalised/autodying system()"; use constant ERROR_IPC_SYS_SIMPLE_OLD => "IPC::System::Simple version %f required for Fatalised/autodying system(). We only have version %f"; use constant ERROR_AUTODIE_CONFLICT => q{"no autodie '%s'" is not allowed while "use Fatal '%s'" is in effect}; use constant ERROR_FATAL_CONFLICT => q{"use Fatal '%s'" is not allowed while "no autodie '%s'" is in effect}; use constant ERROR_58_HINTS => q{Non-subroutine %s hints for %s are not supported under Perl 5.8.x}; # Older versions of IPC::System::Simple don't support all the # features we need. use constant MIN_IPC_SYS_SIMPLE_VER => 0.12; our $VERSION = '2.29'; # VERSION: Generated by DZP::OurPkg::Version our $Debug ||= 0; # EWOULDBLOCK values for systems that don't supply their own. # Even though this is defined with our, that's to help our # test code. Please don't rely upon this variable existing in # the future. our %_EWOULDBLOCK = ( MSWin32 => 33, ); $Carp::CarpInternal{'Fatal'} = 1; $Carp::CarpInternal{'autodie'} = 1; $Carp::CarpInternal{'autodie::exception'} = 1; # the linux parisc port has separate EAGAIN and EWOULDBLOCK, # and the kernel returns EAGAIN my $try_EAGAIN = ($^O eq 'linux' and $Config{archname} =~ /hppa|parisc/) ? 1 : 0; # We have some tags that can be passed in for use with import. # These are all assumed to be CORE:: my %TAGS = ( ':io' => [qw(:dbm :file :filesys :ipc :socket read seek sysread syswrite sysseek )], ':dbm' => [qw(dbmopen dbmclose)], ':file' => [qw(open close flock sysopen fcntl binmode ioctl truncate)], ':filesys' => [qw(opendir closedir chdir link unlink rename mkdir symlink rmdir readlink chmod chown utime)], ':ipc' => [qw(:msg :semaphore :shm pipe kill)], ':msg' => [qw(msgctl msgget msgrcv msgsnd)], ':threads' => [qw(fork)], ':semaphore'=>[qw(semctl semget semop)], ':shm' => [qw(shmctl shmget shmread)], ':system' => [qw(system exec)], # Can we use qw(getpeername getsockname)? What do they do on failure? # TODO - Can socket return false? ':socket' => [qw(accept bind connect getsockopt listen recv send setsockopt shutdown socketpair)], # Our defaults don't include system(), because it depends upon # an optional module, and it breaks the exotic form. # # This *may* change in the future. I'd love IPC::System::Simple # to be a dependency rather than a recommendation, and hence for # system() to be autodying by default. ':default' => [qw(:io :threads)], # Everything in v2.07 and before. This was :default less chmod and chown ':v207' => [qw(:threads :dbm :socket read seek sysread syswrite sysseek open close flock sysopen fcntl fileno binmode ioctl truncate opendir closedir chdir link unlink rename mkdir symlink rmdir readlink umask :msg :semaphore :shm pipe)], # Chmod was added in 2.13 ':v213' => [qw(:v207 chmod)], # chown, utime, kill were added in 2.14 ':v214' => [qw(:v213 chown utime kill)], # umask was removed in 2.26 ':v225' => [qw(:io :threads umask fileno)], # Version specific tags. These allow someone to specify # use autodie qw(:1.994) and know exactly what they'll get. ':1.994' => [qw(:v207)], ':1.995' => [qw(:v207)], ':1.996' => [qw(:v207)], ':1.997' => [qw(:v207)], ':1.998' => [qw(:v207)], ':1.999' => [qw(:v207)], ':1.999_01' => [qw(:v207)], ':2.00' => [qw(:v207)], ':2.01' => [qw(:v207)], ':2.02' => [qw(:v207)], ':2.03' => [qw(:v207)], ':2.04' => [qw(:v207)], ':2.05' => [qw(:v207)], ':2.06' => [qw(:v207)], ':2.06_01' => [qw(:v207)], ':2.07' => [qw(:v207)], # Last release without chmod ':2.08' => [qw(:v213)], ':2.09' => [qw(:v213)], ':2.10' => [qw(:v213)], ':2.11' => [qw(:v213)], ':2.12' => [qw(:v213)], ':2.13' => [qw(:v213)], # Last release without chown ':2.14' => [qw(:v225)], ':2.15' => [qw(:v225)], ':2.16' => [qw(:v225)], ':2.17' => [qw(:v225)], ':2.18' => [qw(:v225)], ':2.19' => [qw(:v225)], ':2.20' => [qw(:v225)], ':2.21' => [qw(:v225)], ':2.22' => [qw(:v225)], ':2.23' => [qw(:v225)], ':2.24' => [qw(:v225)], ':2.25' => [qw(:v225)], ':2.26' => [qw(:default)], ':2.27' => [qw(:default)], ':2.28' => [qw(:default)], ':2.29' => [qw(:default)], ); { # Expand :all immediately by expanding and flattening all tags. # _expand_tag is not really optimised for expanding the ":all" # case (i.e. keys %TAGS, or values %TAGS for that matter), so we # just do it here. # # NB: The %tag_cache/_expand_tag relies on $TAGS{':all'} being # pre-expanded. my %seen; my @all = grep { !/^:/ && !$seen{$_}++ } map { @{$_} } values %TAGS; $TAGS{':all'} = \@all; } # This hash contains subroutines for which we should # subroutine() // die() rather than subroutine() || die() my %Use_defined_or; # CORE::open returns undef on failure. It can legitimately return # 0 on success, eg: open(my $fh, '-|') || exec(...); @Use_defined_or{qw( CORE::fork CORE::recv CORE::send CORE::open CORE::fileno CORE::read CORE::readlink CORE::sysread CORE::syswrite CORE::sysseek CORE::umask )} = (); # Some functions can return true because they changed *some* things, but # not all of them. This is a list of offending functions, and how many # items to subtract from @_ to determine the "success" value they return. my %Returns_num_things_changed = ( 'CORE::chmod' => 1, 'CORE::chown' => 2, 'CORE::kill' => 1, # TODO: Could this return anything on negative args? 'CORE::unlink' => 0, 'CORE::utime' => 2, ); # Optional actions to take on the return value before returning it. my %Retval_action = ( "CORE::open" => q{ # apply the open pragma from our caller if( defined $retval && !( @_ >= 3 && $_[1] =~ /:/ )) { # Get the caller's hint hash my $hints = (caller 0)[10]; # Decide if we're reading or writing and apply the appropriate encoding # These keys are undocumented. # Match what PerlIO_context_layers() does. Read gets the read layer, # everything else gets the write layer. my $encoding = $_[1] =~ /^\+?>/ ? $hints->{"open>"} : $hints->{"open<"}; # Apply the encoding, if any. if( $encoding ) { binmode $_[0], $encoding; } } }, "CORE::sysopen" => q{ # apply the open pragma from our caller if( defined $retval ) { # Get the caller's hint hash my $hints = (caller 0)[10]; require Fcntl; # Decide if we're reading or writing and apply the appropriate encoding. # Match what PerlIO_context_layers() does. Read gets the read layer, # everything else gets the write layer. my $open_read_only = !($_[2] ^ Fcntl::O_RDONLY()); my $encoding = $open_read_only ? $hints->{"open<"} : $hints->{"open>"}; # Apply the encoding, if any. if( $encoding ) { binmode $_[0], $encoding; } } }, ); my %reusable_builtins; # "Wait!" I hear you cry, "truncate() and chdir() are not reuseable! They can # take file and directory handles, which are package depedent." # # You would be correct, except that prototype() returns signatures which don't # allow for passing of globs, and nobody's complained about that. You can # still use \*FILEHANDLE, but that results in a reference coming through, # and it's already pointing to the filehandle in the caller's packge, so # it's all okay. @reusable_builtins{qw( CORE::fork CORE::kill CORE::truncate CORE::chdir CORE::link CORE::unlink CORE::rename CORE::mkdir CORE::symlink CORE::rmdir CORE::readlink CORE::umask CORE::chmod CORE::chown CORE::utime CORE::msgctl CORE::msgget CORE::msgrcv CORE::msgsnd CORE::semctl CORE::semget CORE::semop CORE::shmctl CORE::shmget CORE::shmread CORE::exec CORE::system )} = (); # Cached_fatalised_sub caches the various versions of our # fatalised subs as they're produced. This means we don't # have to build our own replacement of CORE::open and friends # for every single package that wants to use them. my %Cached_fatalised_sub = (); # Every time we're called with package scope, we record the subroutine # (including package or CORE::) in %Package_Fatal. This allows us # to detect illegal combinations of autodie and Fatal, and makes sure # we don't accidently make a Fatal function autodying (which isn't # very useful). my %Package_Fatal = (); # The first time we're called with a user-sub, we cache it here. # In the case of a "no autodie ..." we put back the cached copy. my %Original_user_sub = (); # Is_fatalised_sub simply records a big map of fatalised subroutine # refs. It means we can avoid repeating work, or fatalising something # we've already processed. my %Is_fatalised_sub = (); tie %Is_fatalised_sub, 'Tie::RefHash'; # Our trampoline cache allows us to cache trampolines which are used to # bounce leaked wrapped core subroutines to their actual core counterparts. my %Trampoline_cache; # A cache mapping "CORE::" to their prototype. Turns out that if # you "use autodie;" enough times, this pays off. my %CORE_prototype_cache; # We use our package in a few hash-keys. Having it in a scalar is # convenient. The "guard $PACKAGE" string is used as a key when # setting up lexical guards. my $PACKAGE = __PACKAGE__; my $NO_PACKAGE = "no $PACKAGE"; # Used to detect 'no autodie' # Here's where all the magic happens when someone write 'use Fatal' # or 'use autodie'. sub import { my $class = shift(@_); my @original_args = @_; my $void = 0; my $lexical = 0; my $insist_hints = 0; my ($pkg, $filename) = caller(); @_ or return; # 'use Fatal' is a no-op. # If we see the :lexical flag, then _all_ arguments are # changed lexically if ($_[0] eq LEXICAL_TAG) { $lexical = 1; shift @_; # It is currently an implementation detail that autodie is # implemented as "use Fatal qw(:lexical ...)". For backwards # compatibility, we allow it - but not without a warning. # NB: Optimise for autodie as it is quite possibly the most # freq. consumer of this case. if ($class ne 'autodie' and not $class->isa('autodie')) { if ($class eq 'Fatal') { warnings::warnif( 'deprecated', '[deprecated] The "use Fatal qw(:lexical ...)" ' . 'should be replaced by "use autodie qw(...)". ' . 'Seen' # warnif appends " at <...>" ); } else { warnings::warnif( 'deprecated', "[deprecated] The class/Package $class is a " . 'subclass of Fatal and used the :lexical. ' . 'If $class provides lexical error checking ' . 'it should extend autodie instead of using :lexical. ' . 'Seen' # warnif appends " at <...>" ); } # "Promote" the call to autodie from here on. This is # already mostly the case (e.g. use Fatal qw(:lexical ...) # would throw autodie::exceptions on error rather than the # Fatal errors. $class = 'autodie'; # This requires that autodie is in fact loaded; otherwise # the "$class->X()" method calls below will explode. require autodie; # TODO, when autodie and Fatal are cleanly separated, we # should go a "goto &autodie::import" here instead. } # If we see no arguments and :lexical, we assume they # wanted ':default'. if (@_ == 0) { push(@_, ':default'); } # Don't allow :lexical with :void, it's needlessly confusing. if ( grep { $_ eq VOID_TAG } @_ ) { croak(ERROR_VOID_LEX); } } if ( grep { $_ eq LEXICAL_TAG } @_ ) { # If we see the lexical tag as the non-first argument, complain. croak(ERROR_LEX_FIRST); } my @fatalise_these = @_; # These subs will get unloaded at the end of lexical scope. my %unload_later; # These subs are to be installed into callers namespace. my %install_subs; # Use _translate_import_args to expand tags for us. It will # pass-through unknown tags (i.e. we have to manually handle # VOID_TAG). # # NB: _translate_import_args re-orders everything for us, so # we don't have to worry about stuff like: # # :default :void :io # # That will (correctly) translated into # # expand(:defaults-without-io) :void :io # # by _translate_import_args. for my $func ($class->_translate_import_args(@fatalise_these)) { if ($func eq VOID_TAG) { # When we see :void, set the void flag. $void = 1; } elsif ($func eq INSIST_TAG) { $insist_hints = 1; } else { # Otherwise, fatalise it. # Check to see if there's an insist flag at the front. # If so, remove it, and insist we have hints for this sub. my $insist_this = $insist_hints; if (substr($func, 0, 1) eq '!') { $func = substr($func, 1); $insist_this = 1; } # We're going to make a subroutine fatalistic. # However if we're being invoked with 'use Fatal qw(x)' # and we've already been called with 'no autodie qw(x)' # in the same scope, we consider this to be an error. # Mixing Fatal and autodie effects was considered to be # needlessly confusing on p5p. my $sub = $func; $sub = "${pkg}::$sub" unless $sub =~ /::/; # If we're being called as Fatal, and we've previously # had a 'no X' in scope for the subroutine, then complain # bitterly. if (! $lexical and $^H{$NO_PACKAGE}{$sub}) { croak(sprintf(ERROR_FATAL_CONFLICT, $func, $func)); } # We're not being used in a confusing way, so make # the sub fatal. Note that _make_fatal returns the # old (original) version of the sub, or undef for # built-ins. my $sub_ref = $class->_make_fatal( $func, $pkg, $void, $lexical, $filename, $insist_this, \%install_subs, ); $Original_user_sub{$sub} ||= $sub_ref; # If we're making lexical changes, we need to arrange # for them to be cleaned at the end of our scope, so # record them here. $unload_later{$func} = $sub_ref if $lexical; } } install_subs($pkg, \%install_subs); if ($lexical) { # Dark magic to have autodie work under 5.8 # Copied from namespace::clean, that copied it from # autobox, that found it on an ancient scroll written # in blood. # This magic bit causes %^H to be lexically scoped. $^H |= 0x020000; # Our package guard gets invoked when we leave our lexical # scope. on_end_of_compile_scope(sub { install_subs($pkg, \%unload_later); }); # To allow others to determine when autodie was in scope, # and with what arguments, we also set a %^H hint which # is how we were called. # This feature should be considered EXPERIMENTAL, and # may change without notice. Please e-mail pjf@cpan.org # if you're actually using it. $^H{autodie} = "$PACKAGE @original_args"; } return; } sub unimport { my $class = shift; # Calling "no Fatal" must start with ":lexical" if ($_[0] ne LEXICAL_TAG) { croak(sprintf(ERROR_NO_LEX,$class)); } shift @_; # Remove :lexical my $pkg = (caller)[0]; # If we've been called with arguments, then the developer # has explicitly stated 'no autodie qw(blah)', # in which case, we disable Fatalistic behaviour for 'blah'. my @unimport_these = @_ ? @_ : ':all'; my (%uninstall_subs, %reinstall_subs); for my $symbol ($class->_translate_import_args(@unimport_these)) { my $sub = $symbol; $sub = "${pkg}::$sub" unless $sub =~ /::/; # If 'blah' was already enabled with Fatal (which has package # scope) then, this is considered an error. if (exists $Package_Fatal{$sub}) { croak(sprintf(ERROR_AUTODIE_CONFLICT,$symbol,$symbol)); } # Record 'no autodie qw($sub)' as being in effect. # This is to catch conflicting semantics elsewhere # (eg, mixing Fatal with no autodie) $^H{$NO_PACKAGE}{$sub} = 1; # Record the current sub to be reinstalled at end of scope # and then restore the original (can be undef for "CORE::" # subs) $reinstall_subs{$symbol} = \&$sub; $uninstall_subs{$symbol} = $Original_user_sub{$sub}; } install_subs($pkg, \%uninstall_subs); on_end_of_compile_scope(sub { install_subs($pkg, \%reinstall_subs); }); return; } sub _translate_import_args { my ($class, @args) = @_; my @result; my %seen; if (@args < 2) { # Optimize for this case, as it is fairly common. (e.g. use # autodie; or use autodie qw(:all); both trigger this). return unless @args; # Not a (known) tag, pass through. return @args unless exists($TAGS{$args[0]}); # Strip "CORE::" from all elements in the list as import and # unimport does not handle the "CORE::" prefix too well. # # NB: we use substr as it is faster than s/^CORE::// and # it does not change the elements. return map { substr($_, 6) } @{ $class->_expand_tag($args[0]) }; } # We want to translate # # :default :void :io # # into (pseudo-ish): # # expanded(:threads) :void expanded(:io) # # We accomplish this by "reverse, expand + filter, reverse". for my $a (reverse(@args)) { if (exists $TAGS{$a}) { my $expanded = $class->_expand_tag($a); push(@result, # Remove duplicates after ... grep { !$seen{$_}++ } # we have stripped CORE:: (see above) map { substr($_, 6) } # We take the elements in reverse order # (as @result be reversed later). reverse(@{$expanded})); } else { # pass through - no filtering here for tags. # # The reason for not filtering tags cases like: # # ":default :void :io :void :threads" # # As we have reversed args, we see this as: # # ":threads :void :io :void* :default*" # # (Entries marked with "*" will be filtered out completely). When # reversed again, this will be: # # ":io :void :threads" # # But we would rather want it to be: # # ":void :io :threads" or ":void :io :void :threads" # my $letter = substr($a, 0, 1); if ($letter ne ':' && $a ne INSIST_TAG) { next if $seen{$a}++; if ($letter eq '!' and $seen{substr($a, 1)}++) { my $name = substr($a, 1); # People are being silly and doing: # # use autodie qw(!a a); # # Enjoy this little O(n) clean up... @result = grep { $_ ne $name } @result; } } push @result, $a; } } # Reverse the result to restore the input order return reverse(@result); } # NB: Perl::Critic's dump-autodie-tag-contents depends upon this # continuing to work. { # We assume that $TAGS{':all'} is pre-expanded and just fill it in # from the beginning. my %tag_cache = ( 'all' => [map { "CORE::$_" } @{$TAGS{':all'}}], ); # Expand a given tag (e.g. ":default") into a listref containing # all sub names covered by that tag. Each sub is returned as # "CORE::" (i.e. "CORE::open" rather than "open"). # # NB: the listref must not be modified. sub _expand_tag { my ($class, $tag) = @_; if (my $cached = $tag_cache{$tag}) { return $cached; } if (not exists $TAGS{$tag}) { croak "Invalid exception class $tag"; } my @to_process = @{$TAGS{$tag}}; # If the tag is basically an alias of another tag (like e.g. ":2.11"), # then just share the resulting reference with the original content (so # we only pay for an extra reference for the alias memory-wise). if (@to_process == 1 && substr($to_process[0], 0, 1) eq ':') { # We could do this for "non-tags" as well, but that only occurs # once at the time of writing (":threads" => ["fork"]), so # probably not worth it. my $expanded = $class->_expand_tag($to_process[0]); $tag_cache{$tag} = $expanded; return $expanded; } my %seen = (); my @taglist = (); for my $item (@to_process) { # substr is more efficient than m/^:/ for stuff like this, # at the price of being a bit more verbose/low-level. if (substr($item, 0, 1) eq ':') { # Use recursion here to ensure we expand a tag at most once. my $expanded = $class->_expand_tag($item); push @taglist, grep { !$seen{$_}++ } @{$expanded}; } else { my $subname = "CORE::$item"; push @taglist, $subname unless $seen{$subname}++; } } $tag_cache{$tag} = \@taglist; return \@taglist; } } # This is a backwards compatible version of _write_invocation. It's # recommended you don't use it. sub write_invocation { my ($core, $call, $name, $void, @args) = @_; return Fatal->_write_invocation( $core, $call, $name, $void, 0, # Lexical flag undef, # Sub, unused in legacy mode undef, # Subref, unused in legacy mode. @args ); } # This version of _write_invocation is used internally. It's not # recommended you call it from external code, as the interface WILL # change in the future. sub _write_invocation { my ($class, $core, $call, $name, $void, $lexical, $sub, $sref, @argvs) = @_; if (@argvs == 1) { # No optional arguments my @argv = @{$argvs[0]}; shift @argv; return $class->_one_invocation($core,$call,$name,$void,$sub,! $lexical, $sref, @argv); } else { my $else = "\t"; my (@out, @argv, $n); while (@argvs) { @argv = @{shift @argvs}; $n = shift @argv; my $condition = "\@_ == $n"; if (@argv and $argv[-1] =~ /[#@]_/) { # This argv ends with '@' in the prototype, so it matches # any number of args >= the number of expressions in the # argv. $condition = "\@_ >= $n"; } push @out, "${else}if ($condition) {\n"; $else = "\t} els"; push @out, $class->_one_invocation($core,$call,$name,$void,$sub,! $lexical, $sref, @argv); } push @out, qq[ } die "Internal error: $name(\@_): Do not expect to get ", scalar(\@_), " arguments"; ]; return join '', @out; } } # This is a slim interface to ensure backward compatibility with # anyone doing very foolish things with old versions of Fatal. sub one_invocation { my ($core, $call, $name, $void, @argv) = @_; return Fatal->_one_invocation( $core, $call, $name, $void, undef, # Sub. Unused in back-compat mode. 1, # Back-compat flag undef, # Subref, unused in back-compat mode. @argv ); } # This is the internal interface that generates code. # NOTE: This interface WILL change in the future. Please do not # call this subroutine directly. # TODO: Whatever's calling this code has already looked up hints. Pass # them in, rather than look them up a second time. sub _one_invocation { my ($class, $core, $call, $name, $void, $sub, $back_compat, $sref, @argv) = @_; # If someone is calling us directly (a child class perhaps?) then # they could try to mix void without enabling backwards # compatibility. We just don't support this at all, so we gripe # about it rather than doing something unwise. if ($void and not $back_compat) { Carp::confess("Internal error: :void mode not supported with $class"); } # @argv only contains the results of the in-built prototype # function, and is therefore safe to interpolate in the # code generators below. # TODO - The following clobbers context, but that's what the # old Fatal did. Do we care? if ($back_compat) { # Use Fatal qw(system) will never be supported. It generated # a compile-time error with legacy Fatal, and there's no reason # to support it when autodie does a better job. if ($call eq 'CORE::system') { return q{ croak("UNIMPLEMENTED: use Fatal qw(system) not supported."); }; } local $" = ', '; if ($void) { return qq/return (defined wantarray)?$call(@argv): $call(@argv) || Carp::croak("Can't $name(\@_)/ . ($core ? ': $!' : ', \$! is \"$!\"') . '")' } else { return qq{return $call(@argv) || Carp::croak("Can't $name(\@_)} . ($core ? ': $!' : ', \$! is \"$!\"') . '")'; } } # The name of our original function is: # $call if the function is CORE # $sub if our function is non-CORE # The reason for this is that $call is what we're actually # calling. For our core functions, this is always # CORE::something. However for user-defined subs, we're about to # replace whatever it is that we're calling; as such, we actually # calling a subroutine ref. my $human_sub_name = $core ? $call : $sub; # Should we be testing to see if our result is defined, or # just true? my $use_defined_or; my $hints; # All user-sub hints, including list hints. if ( $core ) { # Core hints are built into autodie. $use_defined_or = exists ( $Use_defined_or{$call} ); } else { # User sub hints are looked up using autodie::hints, # since users may wish to add their own hints. require autodie::hints; $hints = autodie::hints->get_hints_for( $sref ); # We'll look up the sub's fullname. This means we # get better reports of where it came from in our # error messages, rather than what imported it. $human_sub_name = autodie::hints->sub_fullname( $sref ); } # Checks for special core subs. if ($call eq 'CORE::system') { # Leverage IPC::System::Simple if we're making an autodying # system. local $" = ", "; # We need to stash $@ into $E, rather than using # local $@ for the whole sub. If we don't then # any exceptions from internal errors in autodie/Fatal # will mysteriously disappear before propagating # upwards. return qq{ my \$retval; my \$E; { local \$@; eval { \$retval = IPC::System::Simple::system(@argv); }; \$E = \$@; } if (\$E) { # TODO - This can't be overridden in child # classes! die autodie::exception::system->new( function => q{CORE::system}, args => [ @argv ], message => "\$E", errno => \$!, ); } return \$retval; }; } local $" = ', '; # If we're going to throw an exception, here's the code to use. my $die = qq{ die $class->throw( function => q{$human_sub_name}, args => [ @argv ], pragma => q{$class}, errno => \$!, context => \$context, return => \$retval, eval_error => \$@ ) }; if ($call eq 'CORE::flock') { # flock needs special treatment. When it fails with # LOCK_UN and EWOULDBLOCK, then it's not really fatal, it just # means we couldn't get the lock right now. require POSIX; # For POSIX::EWOULDBLOCK local $@; # Don't blat anyone else's $@. # Ensure that our vendor supports EWOULDBLOCK. If they # don't (eg, Windows), then we use known values for its # equivalent on other systems. my $EWOULDBLOCK = eval { POSIX::EWOULDBLOCK(); } || $_EWOULDBLOCK{$^O} || _autocroak("Internal error - can't overload flock - EWOULDBLOCK not defined on this system."); my $EAGAIN = $EWOULDBLOCK; if ($try_EAGAIN) { $EAGAIN = eval { POSIX::EAGAIN(); } || _autocroak("Internal error - can't overload flock - EAGAIN not defined on this system."); } require Fcntl; # For Fcntl::LOCK_NB return qq{ my \$context = wantarray() ? "list" : "scalar"; # Try to flock. If successful, return it immediately. my \$retval = $call(@argv); return \$retval if \$retval; # If we failed, but we're using LOCK_NB and # returned EWOULDBLOCK, it's not a real error. if (\$_[1] & Fcntl::LOCK_NB() and (\$! == $EWOULDBLOCK or ($try_EAGAIN and \$! == $EAGAIN ))) { return \$retval; } # Otherwise, we failed. Die noisily. $die; }; } if (exists $Returns_num_things_changed{$call}) { # Some things return the number of things changed (like # chown, kill, chmod, etc). We only consider these successful # if *all* the things are changed. return qq[ my \$num_things = \@_ - $Returns_num_things_changed{$call}; my \$retval = $call(@argv); if (\$retval != \$num_things) { # We need \$context to throw an exception. # It's *always* set to scalar, because that's how # autodie calls chown() above. my \$context = "scalar"; $die; } return \$retval; ]; } # AFAIK everything that can be given an unopned filehandle # will fail if it tries to use it, so we don't really need # the 'unopened' warning class here. Especially since they # then report the wrong line number. # Other warnings are disabled because they produce excessive # complaints from smart-match hints under 5.10.1. my $code = qq[ no warnings qw(unopened uninitialized numeric); no if \$\] >= 5.017011, warnings => "experimental::smartmatch"; if (wantarray) { my \@results = $call(@argv); my \$retval = \\\@results; my \$context = "list"; ]; my $retval_action = $Retval_action{$call} || ''; if ( $hints and ( ref($hints->{list} ) || "" ) eq 'CODE' ) { # NB: Subroutine hints are passed as a full list. # This differs from the 5.10.0 smart-match behaviour, # but means that context unaware subroutines can use # the same hints in both list and scalar context. $code .= qq{ if ( \$hints->{list}->(\@results) ) { $die }; }; } elsif ( PERL510 and $hints ) { $code .= qq{ if ( \@results ~~ \$hints->{list} ) { $die }; }; } elsif ( $hints ) { croak sprintf(ERROR_58_HINTS, 'list', $sub); } else { $code .= qq{ # An empty list, or a single undef is failure if (! \@results or (\@results == 1 and ! defined \$results[0])) { $die; } } } # Tidy up the end of our wantarray call. $code .= qq[ return \@results; } ]; # Otherwise, we're in scalar context. # We're never in a void context, since we have to look # at the result. $code .= qq{ my \$retval = $call(@argv); my \$context = "scalar"; }; if ( $hints and ( ref($hints->{scalar} ) || "" ) eq 'CODE' ) { # We always call code refs directly, since that always # works in 5.8.x, and always works in 5.10.1 return $code .= qq{ if ( \$hints->{scalar}->(\$retval) ) { $die }; $retval_action return \$retval; }; } elsif (PERL510 and $hints) { return $code . qq{ if ( \$retval ~~ \$hints->{scalar} ) { $die }; $retval_action return \$retval; }; } elsif ( $hints ) { croak sprintf(ERROR_58_HINTS, 'scalar', $sub); } return $code . ( $use_defined_or ? qq{ $die if not defined \$retval; $retval_action return \$retval; } : qq{ $retval_action return \$retval || $die; } ) ; } # This returns the old copy of the sub, so we can # put it back at end of scope. # TODO : Check to make sure prototypes are restored correctly. # TODO: Taking a huge list of arguments is awful. Rewriting to # take a hash would be lovely. # TODO - BACKCOMPAT - This is not yet compatible with 5.10.0 sub _make_fatal { my($class, $sub, $pkg, $void, $lexical, $filename, $insist, $install_subs) = @_; my($code, $sref, $proto, $core, $call, $hints, $cache, $cache_type); my $ini = $sub; my $name = $sub; if (index($sub, '::') == -1) { $sub = "${pkg}::$sub"; if (substr($name, 0, 1) eq '&') { $name = substr($name, 1); } } else { $name =~ s/.*:://; } # Figure if we're using lexical or package semantics and # twiddle the appropriate bits. if (not $lexical) { $Package_Fatal{$sub} = 1; } # TODO - We *should* be able to do skipping, since we know when # we've lexicalised / unlexicalised a subroutine. warn "# _make_fatal: sub=$sub pkg=$pkg name=$name void=$void\n" if $Debug; croak(sprintf(ERROR_BADNAME, $class, $name)) unless $name =~ /^\w+$/; if (defined(&$sub)) { # user subroutine # NOTE: Previously we would localise $@ at this point, so # the following calls to eval {} wouldn't interfere with anything # that's already in $@. Unfortunately, it would also stop # any of our croaks from triggering(!), which is even worse. # This could be something that we've fatalised that # was in core. # Store the current sub in case we need to restore it. $sref = \&$sub; if ( $Package_Fatal{$sub} and exists($CORE_prototype_cache{"CORE::$name"})) { # Something we previously made Fatal that was core. # This is safe to replace with an autodying to core # version. $core = 1; $call = "CORE::$name"; $proto = $CORE_prototype_cache{$call}; # We return our $sref from this subroutine later # on, indicating this subroutine should be placed # back when we're finished. } else { # If this is something we've already fatalised or played with, # then look-up the name of the original sub for the rest of # our processing. if (exists($Is_fatalised_sub{$sref})) { # $sub is one of our wrappers around a CORE sub or a # user sub. Instead of wrapping our wrapper, lets just # generate a new wrapper for the original sub. # - NB: the current wrapper might be for a different class # than the one we are generating now (e.g. some limited # mixing between use Fatal + use autodie can occur). # - Even for nested autodie, we need this as the leak guards # differ. my $s = $Is_fatalised_sub{$sref}; if (defined($s)) { # It is a wrapper for a user sub $sub = $s; } else { # It is a wrapper for a CORE:: sub $core = 1; $call = "CORE::$name"; $proto = $CORE_prototype_cache{$call}; } } # A regular user sub, or a user sub wrapping a # core sub. if (!$core) { # A non-CORE sub might have hints and such... $proto = prototype($sref); $call = '&$sref'; require autodie::hints; $hints = autodie::hints->get_hints_for( $sref ); # If we've insisted on hints, but don't have them, then # bail out! if ($insist and not $hints) { croak(sprintf(ERROR_NOHINTS, $name)); } # Otherwise, use the default hints if we don't have # any. $hints ||= autodie::hints::DEFAULT_HINTS(); } } } elsif ($sub eq $ini && $sub !~ /^CORE::GLOBAL::/) { # Stray user subroutine croak(sprintf(ERROR_NOTSUB,$sub)); } elsif ($name eq 'system') { # If we're fatalising system, then we need to load # helper code. # The business with $E is to avoid clobbering our caller's # $@, and to avoid $@ being localised when we croak. my $E; { local $@; eval { require IPC::System::Simple; # Only load it if we need it. require autodie::exception::system; }; $E = $@; } if ($E) { croak ERROR_NO_IPC_SYS_SIMPLE; } # Make sure we're using a recent version of ISS that actually # support fatalised system. if ($IPC::System::Simple::VERSION < MIN_IPC_SYS_SIMPLE_VER) { croak sprintf( ERROR_IPC_SYS_SIMPLE_OLD, MIN_IPC_SYS_SIMPLE_VER, $IPC::System::Simple::VERSION ); } $call = 'CORE::system'; $core = 1; } elsif ($name eq 'exec') { # Exec doesn't have a prototype. We don't care. This # breaks the exotic form with lexical scope, and gives # the regular form a "do or die" behavior as expected. $call = 'CORE::exec'; $core = 1; } else { # CORE subroutine $call = "CORE::$name"; if (exists($CORE_prototype_cache{$call})) { $proto = $CORE_prototype_cache{$call}; } else { my $E; { local $@; $proto = eval { prototype $call }; $E = $@; } croak(sprintf(ERROR_NOT_BUILT,$name)) if $E; croak(sprintf(ERROR_CANT_OVERRIDE,$name)) if not defined $proto; $CORE_prototype_cache{$call} = $proto; } $core = 1; } # TODO: This caching works, but I don't like using $void and # $lexical as keys. In particular, I suspect our code may end up # wrapping already wrapped code when autodie and Fatal are used # together. # NB: We must use '$sub' (the name plus package) and not # just '$name' (the short name) here. Failing to do so # results code that's in the wrong package, and hence has # access to the wrong package filehandles. $cache = $Cached_fatalised_sub{$class}{$sub}; if ($lexical) { $cache_type = CACHE_AUTODIE_LEAK_GUARD; } else { $cache_type = CACHE_FATAL_WRAPPER; $cache_type = CACHE_FATAL_VOID if $void; } if (my $subref = $cache->{$cache_type}) { $install_subs->{$name} = $subref; return $sref; } # If our subroutine is reusable (ie, not package depdendent), # then check to see if we've got a cached copy, and use that. # See RT #46984. (Thanks to Niels Thykier for being awesome!) if ($core && exists $reusable_builtins{$call}) { # For non-lexical subs, we can just use this cache directly # - for lexical variants, we need a leak guard as well. $code = $reusable_builtins{$call}{$lexical}; if (!$lexical && defined($code)) { $install_subs->{$name} = $code; return $sref; } } if (!($lexical && $core) && !defined($code)) { # No code available, generate it now. my $wrapper_pkg = $pkg; $wrapper_pkg = undef if (exists($reusable_builtins{$call})); $code = $class->_compile_wrapper($wrapper_pkg, $core, $call, $name, $void, $lexical, $sub, $sref, $hints, $proto); if (!defined($wrapper_pkg)) { # cache it so we don't recompile this part again $reusable_builtins{$call}{$lexical} = $code; } } # Now we need to wrap our fatalised sub inside an itty bitty # closure, which can detect if we've leaked into another file. # Luckily, we only need to do this for lexical (autodie) # subs. Fatal subs can leak all they want, it's considered # a "feature" (or at least backwards compatible). # TODO: Cache our leak guards! # TODO: This is pretty hairy code. A lot more tests would # be really nice for this. my $installed_sub = $code; if ($lexical) { $installed_sub = $class->_make_leak_guard($filename, $code, $sref, $call, $pkg, $proto); } $cache->{$cache_type} = $code; $install_subs->{$name} = $installed_sub; # Cache that we've now overridden this sub. If we get called # again, we may need to find that find subroutine again (eg, for hints). $Is_fatalised_sub{$installed_sub} = $sref; return $sref; } # This subroutine exists primarily so that child classes can override # it to point to their own exception class. Doing this is significantly # less complex than overriding throw() sub exception_class { return "autodie::exception" }; { my %exception_class_for; my %class_loaded; sub throw { my ($class, @args) = @_; # Find our exception class if we need it. my $exception_class = $exception_class_for{$class} ||= $class->exception_class; if (not $class_loaded{$exception_class}) { if ($exception_class =~ /[^\w:']/) { confess "Bad exception class '$exception_class'.\nThe '$class->exception_class' method wants to use $exception_class\nfor exceptions, but it contains characters which are not word-characters or colons."; } # Alas, Perl does turn barewords into modules unless they're # actually barewords. As such, we're left doing a string eval # to make sure we load our file correctly. my $E; { local $@; # We can't clobber $@, it's wrong! my $pm_file = $exception_class . ".pm"; $pm_file =~ s{ (?: :: | ' ) }{/}gx; eval { require $pm_file }; $E = $@; # Save $E despite ending our local. } # We need quotes around $@ to make sure it's stringified # while still in scope. Without them, we run the risk of # $@ having been cleared by us exiting the local() block. confess "Failed to load '$exception_class'.\nThis may be a typo in the '$class->exception_class' method,\nor the '$exception_class' module may not exist.\n\n $E" if $E; $class_loaded{$exception_class}++; } return $exception_class->new(@args); } } # Creates and returns a leak guard (with prototype if needed). sub _make_leak_guard { my ($class, $filename, $wrapped_sub, $orig_sub, $call, $pkg, $proto) = @_; # The leak guard is rather lengthly (in fact it makes up the most # of _make_leak_guard). It is possible to split it into a large # "generic" part and a small wrapper with call-specific # information. This was done in v2.19 and profiling suggested # that we ended up using a substantial amount of runtime in "goto" # between the leak guard(s) and the final sub. Therefore, the two # parts were merged into one to reduce the runtime overhead. my $leak_guard = sub { my $caller_level = 0; my $caller; while ( ($caller = (caller $caller_level)[1]) =~ m{^\(eval \d+\)$} ) { # If our filename is actually an eval, and we # reach it, then go to our autodying code immediatately. last if ($caller eq $filename); $caller_level++; } # We're now out of the eval stack. if ($caller eq $filename) { # No leak, call the wrapper. NB: In this case, it doesn't # matter if it is a CORE sub or not. if (!defined($wrapped_sub)) { # CORE sub that we were too lazy to compile when we # created this leak guard. die "$call is not CORE::" if substr($call, 0, 6) ne 'CORE::'; my $name = substr($call, 6); my $sub = $name; my $lexical = 1; my $wrapper_pkg = $pkg; my $code; if (exists($reusable_builtins{$call})) { $code = $reusable_builtins{$call}{$lexical}; $wrapper_pkg = undef; } if (!defined($code)) { $code = $class->_compile_wrapper($wrapper_pkg, 1, # core $call, $name, 0, # void $lexical, $sub, undef, # subref (not used for core) undef, # hints (not used for core) $proto); if (!defined($wrapper_pkg)) { # cache it so we don't recompile this part again $reusable_builtins{$call}{$lexical} = $code; } } # As $wrapped_sub is "closed over", updating its value will # be "remembered" for the next call. $wrapped_sub = $code; } goto $wrapped_sub; } # We leaked, time to call the original function. # - for non-core functions that will be $orig_sub # - for CORE functions, $orig_sub may be a trampoline goto $orig_sub if defined($orig_sub); # We are wrapping a CORE sub and we do not have a trampoline # yet. # # If we've cached a trampoline, then use it. Usually only # resuable subs will have cache hits, but non-reusuably ones # can get it as well in (very) rare cases. It is mostly in # cases where a package uses autodie multiple times and leaks # from multiple places. Possibly something like: # # package Pkg::With::LeakyCode; # sub a { # use autodie; # code_that_leaks(); # } # # sub b { # use autodie; # more_leaky_code(); # } # # Note that we use "Fatal" as package name for reusable subs # because A) that allows us to trivially re-use the # trampolines as well and B) because the reusable sub is # compiled into "package Fatal" as well. $pkg = 'Fatal' if exists $reusable_builtins{$call}; $orig_sub = $Trampoline_cache{$pkg}{$call}; if (not $orig_sub) { # If we don't have a trampoline, we need to build it. # # We only generate trampolines when we need them, and # we can cache them by subroutine + package. # # As $orig_sub is "closed over", updating its value will # be "remembered" for the next call. $orig_sub = make_core_trampoline($call, $pkg, $proto); # We still cache it despite remembering it in $orig_sub as # well. In particularly, we rely on this to avoid # re-compiling the reusable trampolines. $Trampoline_cache{$pkg}{$call} = $orig_sub; } # Bounce to our trampoline, which takes us to our core sub. goto $orig_sub; }; # <-- end of leak guard # If there is a prototype on the original sub, copy it to the leak # guard. if (defined $proto) { # The "\&" may appear to be redundant but set_prototype # croaks when it is removed. set_prototype(\&$leak_guard, $proto); } return $leak_guard; } sub _compile_wrapper { my ($class, $wrapper_pkg, $core, $call, $name, $void, $lexical, $sub, $sref, $hints, $proto) = @_; my $real_proto = ''; my @protos; my $code; if (defined $proto) { $real_proto = " ($proto)"; } else { $proto = '@'; } @protos = fill_protos($proto); $code = qq[ sub$real_proto { ]; if (!$lexical) { $code .= q[ local($", $!) = (', ', 0); ]; } # Don't have perl whine if exec fails, since we'll be handling # the exception now. $code .= "no warnings qw(exec);\n" if $call eq "CORE::exec"; $code .= $class->_write_invocation($core, $call, $name, $void, $lexical, $sub, $sref, @protos); $code .= "}\n"; warn $code if $Debug; # I thought that changing package was a monumental waste of # time for CORE subs, since they'll always be the same. However # that's not the case, since they may refer to package-based # filehandles (eg, with open). # # The %reusable_builtins hash defines ones we can aggressively # cache as they never depend upon package-based symbols. my $E; { no strict 'refs'; ## no critic # to avoid: Can't use string (...) as a symbol ref ... local $@; if (defined($wrapper_pkg)) { $code = eval("package $wrapper_pkg; require Carp; $code"); ## no critic } else { $code = eval("require Carp; $code"); ## no critic } $E = $@; } if (not $code) { my $true_name = $core ? $call : $sub; croak("Internal error in autodie/Fatal processing $true_name: $E"); } return $code; } # For some reason, dying while replacing our subs doesn't # kill our calling program. It simply stops the loading of # autodie and keeps going with everything else. The _autocroak # sub allows us to die with a vengeance. It should *only* ever be # used for serious internal errors, since the results of it can't # be captured. sub _autocroak { warn Carp::longmess(@_); exit(255); # Ugh! } 1; __END__ require 5.004; package Test; use strict; use Carp; our ($VERSION, @ISA, @EXPORT, @EXPORT_OK, $ntest, $TestLevel); #public-is our ($TESTOUT, $TESTERR, %Program_Lines, $told_about_diff, $ONFAIL, %todo, %history, $planned, @FAILDETAIL); #private-ish # In case a test is run in a persistent environment. sub _reset_globals { %todo = (); %history = (); @FAILDETAIL = (); $ntest = 1; $TestLevel = 0; # how many extra stack frames to skip $planned = 0; } $VERSION = '1.31'; require Exporter; @ISA=('Exporter'); @EXPORT = qw(&plan &ok &skip); @EXPORT_OK = qw($ntest $TESTOUT $TESTERR); $|=1; $TESTOUT = *STDOUT{IO}; $TESTERR = *STDERR{IO}; # Use of this variable is strongly discouraged. It is set mainly to # help test coverage analyzers know which test is running. $ENV{REGRESSION_TEST} = $0; sub plan { croak "Test::plan(%args): odd number of arguments" if @_ & 1; croak "Test::plan(): should not be called more than once" if $planned; local($\, $,); # guard against -l and other things that screw with # print _reset_globals(); _read_program( (caller)[1] ); my $max=0; while (@_) { my ($k,$v) = splice(@_, 0, 2); if ($k =~ /^test(s)?$/) { $max = $v; } elsif ($k eq 'todo' or $k eq 'failok') { for (@$v) { $todo{$_}=1; }; } elsif ($k eq 'onfail') { ref $v eq 'CODE' or croak "Test::plan(onfail => $v): must be CODE"; $ONFAIL = $v; } else { carp "Test::plan(): skipping unrecognized directive '$k'" } } my @todo = sort { $a <=> $b } keys %todo; if (@todo) { print $TESTOUT "1..$max todo ".join(' ', @todo).";\n"; } else { print $TESTOUT "1..$max\n"; } ++$planned; print $TESTOUT "# Running under perl version $] for $^O", (chr(65) eq 'A') ? "\n" : " in a non-ASCII world\n"; print $TESTOUT "# Win32::BuildNumber ", &Win32::BuildNumber(), "\n" if defined(&Win32::BuildNumber) and defined &Win32::BuildNumber(); print $TESTOUT "# MacPerl version $MacPerl::Version\n" if defined $MacPerl::Version; printf $TESTOUT "# Current time local: %s\n# Current time GMT: %s\n", scalar(localtime($^T)), scalar(gmtime($^T)); print $TESTOUT "# Using Test.pm version $VERSION\n"; # Retval never used: return undef; } sub _read_program { my($file) = shift; return unless defined $file and length $file and -e $file and -f _ and -r _; open(SOURCEFILE, '<', $file) || return; $Program_Lines{$file} = []; close(SOURCEFILE); foreach my $x (@{$Program_Lines{$file}}) { $x =~ tr/\cm\cj\n\r//d } unshift @{$Program_Lines{$file}}, ''; return 1; } sub _to_value { my ($v) = @_; return ref $v eq 'CODE' ? $v->() : $v; } sub _quote { my $str = $_[0]; return "" unless defined $str; $str =~ s/\\/\\\\/g; $str =~ s/"/\\"/g; $str =~ s/\a/\\a/g; $str =~ s/[\b]/\\b/g; $str =~ s/\e/\\e/g; $str =~ s/\f/\\f/g; $str =~ s/\n/\\n/g; $str =~ s/\r/\\r/g; $str =~ s/\t/\\t/g; if (defined $^V && $^V ge v5.6) { $str =~ s/([[:cntrl:]])(?!\d)/sprintf('\\%o',ord($1))/eg; $str =~ s/([[:^print:]])/sprintf('\\x%02X',ord($1))/eg; $str =~ s/([[:^ascii:]])/sprintf('\\x{%X}',ord($1))/eg; } elsif (ord("A") == 65) { $str =~ s/([\0-\037])(?!\d)/sprintf('\\%o',ord($1))/eg; $str =~ s/([\0-\037\177-\377])/sprintf('\\x%02X',ord($1))/eg; $str =~ s/([^\0-\176])/sprintf('\\x{%X}',ord($1))/eg; } else { # Assuming EBCDIC on this ancient Perl # The controls except for one are 0-\077, so almost all controls on # EBCDIC platforms will be expressed in octal, instead of just the C0 # ones. $str =~ s/([\0-\077])(?!\d)/sprintf('\\%o',ord($1))/eg; $str =~ s/([\0-\077])/sprintf('\\x%02X',ord($1))/eg; $str =~ s/([^\0-\xFF])/sprintf('\\x{%X}',ord($1))/eg; # What remains to be escaped are the non-ASCII-range characters, # including the one control that isn't in the 0-077 range. # (We don't escape further any ASCII printables.) $str =~ s<[^ !"\$\%#'()*+,\-./0123456789:;\<=\>?\@ABCDEFGHIJKLMNOPQRSTUVWXYZ\[\\\]^_`abcdefghijklmnopqrstuvwxyz{|}~]>eg; } #if( $_[1] ) { # substr( $str , 218-3 ) = "..." # if length($str) >= 218 and !$ENV{PERL_TEST_NO_TRUNC}; #} return qq("$str"); } # A past maintainer of this module said: # <> # sub ok ($;$$) { croak "ok: plan before you test!" if !$planned; local($\,$,); # guard against -l and other things that screw with # print my ($pkg,$file,$line) = caller($TestLevel); my $repetition = ++$history{"$file:$line"}; my $context = ("$file at line $line". ($repetition > 1 ? " fail \#$repetition" : '')); # Are we comparing two values? my $compare = 0; my $ok=0; my $result = _to_value(shift); my ($expected, $isregex, $regex); if (@_ == 0) { $ok = $result; } else { $compare = 1; $expected = _to_value(shift); if (!defined $expected) { $ok = !defined $result; } elsif (!defined $result) { $ok = 0; } elsif (ref($expected) eq 'Regexp') { $ok = $result =~ /$expected/; $regex = $expected; } elsif (($regex) = ($expected =~ m,^ / (.+) / $,sx) or (undef, $regex) = ($expected =~ m,^ m([^\w\s]) (.+) \1 $,sx)) { $ok = $result =~ /$regex/; } else { $ok = $result eq $expected; } } my $todo = $todo{$ntest}; if ($todo and $ok) { $context .= ' TODO?!' if $todo; print $TESTOUT "ok $ntest # ($context)\n"; } else { # Issuing two seperate prints() causes problems on VMS. if (!$ok) { print $TESTOUT "not ok $ntest\n"; } else { print $TESTOUT "ok $ntest\n"; } $ok or _complain($result, $expected, { 'repetition' => $repetition, 'package' => $pkg, 'result' => $result, 'todo' => $todo, 'file' => $file, 'line' => $line, 'context' => $context, 'compare' => $compare, @_ ? ('diagnostic' => _to_value(shift)) : (), }); } ++ $ntest; $ok; } sub _complain { my($result, $expected, $detail) = @_; $$detail{expected} = $expected if defined $expected; # Get the user's diagnostic, protecting against multi-line # diagnostics. my $diag = $$detail{diagnostic}; $diag =~ s/\n/\n#/g if defined $diag; my $out = $$detail{todo} ? $TESTOUT : $TESTERR; $$detail{context} .= ' *TODO*' if $$detail{todo}; if (!$$detail{compare}) { if (!$diag) { print $out "# Failed test $ntest in $$detail{context}\n"; } else { print $out "# Failed test $ntest in $$detail{context}: $diag\n"; } } else { my $prefix = "Test $ntest"; print $out "# $prefix got: " . _quote($result) . " ($$detail{context})\n"; $prefix = ' ' x (length($prefix) - 5); my $expected_quoted = (defined $$detail{regex}) ? 'qr{'.($$detail{regex}).'}' : _quote($expected); print $out "# $prefix Expected: $expected_quoted", $diag ? " ($diag)" : (), "\n"; _diff_complain( $result, $expected, $detail, $prefix ) if defined($expected) and 2 < ($expected =~ tr/\n//); } if(defined $Program_Lines{ $$detail{file} }[ $$detail{line} ]) { print $out "# $$detail{file} line $$detail{line} is: $Program_Lines{ $$detail{file} }[ $$detail{line} ]\n" if $Program_Lines{ $$detail{file} }[ $$detail{line} ] =~ m/[^\s\#\(\)\{\}\[\]\;]/; # Otherwise it's uninformative undef $Program_Lines{ $$detail{file} }[ $$detail{line} ]; # So we won't repeat it. } push @FAILDETAIL, $detail; return; } sub _diff_complain { my($result, $expected, $detail, $prefix) = @_; return _diff_complain_external(@_) if $ENV{PERL_TEST_DIFF}; return _diff_complain_algdiff(@_) if eval { local @INC = @INC; pop @INC if $INC[-1] eq '.'; require Algorithm::Diff; Algorithm::Diff->VERSION(1.15); 1; }; $told_about_diff++ or print $TESTERR <<"EOT"; # $prefix (Install the Algorithm::Diff module to have differences in multiline # $prefix output explained. You might also set the PERL_TEST_DIFF environment # $prefix variable to run a diff program on the output.) EOT ; return; } sub _diff_complain_external { my($result, $expected, $detail, $prefix) = @_; my $diff = $ENV{PERL_TEST_DIFF} || die "WHAAAA?"; require File::Temp; my($got_fh, $got_filename) = File::Temp::tempfile("test-got-XXXXX"); my($exp_fh, $exp_filename) = File::Temp::tempfile("test-exp-XXXXX"); unless ($got_fh && $exp_fh) { warn "Can't get tempfiles"; return; } print $got_fh $result; print $exp_fh $expected; if (close($got_fh) && close($exp_fh)) { my $diff_cmd = "$diff $exp_filename $got_filename"; print $TESTERR "#\n# $prefix $diff_cmd\n"; if (open(DIFF, '-|', $diff_cmd)) { local $_; while () { print $TESTERR "# $prefix $_"; } close(DIFF); } else { warn "Can't run diff: $!"; } } else { warn "Can't write to tempfiles: $!"; } unlink($got_filename); unlink($exp_filename); return; } sub _diff_complain_algdiff { my($result, $expected, $detail, $prefix) = @_; my @got = split(/^/, $result); my @exp = split(/^/, $expected); my $diff_kind; my @diff_lines; my $diff_flush = sub { return unless $diff_kind; my $count_lines = @diff_lines; my $s = $count_lines == 1 ? "" : "s"; my $first_line = $diff_lines[0][0] + 1; print $TESTERR "# $prefix "; if ($diff_kind eq "GOT") { print $TESTERR "Got $count_lines extra line$s at line $first_line:\n"; for my $i (@diff_lines) { print $TESTERR "# $prefix + " . _quote($got[$i->[0]]) . "\n"; } } elsif ($diff_kind eq "EXP") { if ($count_lines > 1) { my $last_line = $diff_lines[-1][0] + 1; print $TESTERR "Lines $first_line-$last_line are"; } else { print $TESTERR "Line $first_line is"; } print $TESTERR " missing:\n"; for my $i (@diff_lines) { print $TESTERR "# $prefix - " . _quote($exp[$i->[1]]) . "\n"; } } elsif ($diff_kind eq "CH") { if ($count_lines > 1) { my $last_line = $diff_lines[-1][0] + 1; print $TESTERR "Lines $first_line-$last_line are"; } else { print $TESTERR "Line $first_line is"; } print $TESTERR " changed:\n"; for my $i (@diff_lines) { print $TESTERR "# $prefix - " . _quote($exp[$i->[1]]) . "\n"; print $TESTERR "# $prefix + " . _quote($got[$i->[0]]) . "\n"; } } # reset $diff_kind = undef; @diff_lines = (); }; my $diff_collect = sub { my $kind = shift; &$diff_flush() if $diff_kind && $diff_kind ne $kind; $diff_kind = $kind; push(@diff_lines, [@_]); }; Algorithm::Diff::traverse_balanced( \@got, \@exp, { DISCARD_A => sub { &$diff_collect("GOT", @_) }, DISCARD_B => sub { &$diff_collect("EXP", @_) }, CHANGE => sub { &$diff_collect("CH", @_) }, MATCH => sub { &$diff_flush() }, }, ); &$diff_flush(); return; } #~`~`~`~`~`~`~`~`~`~`~`~`~`~`~`~`~`~`~`~`~`~`~`~`~`~`~`~`~`~`~`~`~`~`~`~`~`~ sub skip ($;$$$) { local($\, $,); # guard against -l and other things that screw with # print my $whyskip = _to_value(shift); if (!@_ or $whyskip) { $whyskip = '' if $whyskip =~ m/^\d+$/; $whyskip =~ s/^[Ss]kip(?:\s+|$)//; # backwards compatibility, old # versions required the reason # to start with 'skip' # We print in one shot for VMSy reasons. my $ok = "ok $ntest # skip"; $ok .= " $whyskip" if length $whyskip; $ok .= "\n"; print $TESTOUT $ok; ++ $ntest; return 1; } else { # backwards compatibility (I think). skip() used to be # called like ok(), which is weird. I haven't decided what to do with # this yet. # warn <(\@FAILDETAIL) if @FAILDETAIL && $ONFAIL; } 1; __END__ # "Your mistake was a hidden intention." # -- /Oblique Strategies/, Brian Eno and Peter Schmidt package NEXT; use Carp; use strict; use warnings; use overload (); our $VERSION = '0.67_01'; sub NEXT::ELSEWHERE::ancestors { my @inlist = shift; my @outlist = (); while (my $next = shift @inlist) { push @outlist, $next; no strict 'refs'; unshift @inlist, @{"$outlist[-1]::ISA"}; } return @outlist; } sub NEXT::ELSEWHERE::ordered_ancestors { my @inlist = shift; my @outlist = (); while (my $next = shift @inlist) { push @outlist, $next; no strict 'refs'; push @inlist, @{"$outlist[-1]::ISA"}; } return sort { $a->isa($b) ? -1 : $b->isa($a) ? +1 : 0 } @outlist; } sub NEXT::ELSEWHERE::buildAUTOLOAD { my $autoload_name = caller() . '::AUTOLOAD'; no strict 'refs'; *{$autoload_name} = sub { my ($self) = @_; my $depth = 1; until (((caller($depth))[3]||q{}) !~ /^\(eval\)$/) { $depth++ } my $caller = (caller($depth))[3]; my $wanted = $NEXT::AUTOLOAD || $autoload_name; undef $NEXT::AUTOLOAD; my ($caller_class, $caller_method) = do { $caller =~ m{(.*)::(.*)}g }; my ($wanted_class, $wanted_method) = do { $wanted =~ m{(.*)::(.*)}g }; croak "Can't call $wanted from $caller" unless $caller_method eq $wanted_method; my $key = ref $self && overload::Overloaded($self) ? overload::StrVal($self) : $self; local ($NEXT::NEXT{$key,$wanted_method}, $NEXT::SEEN) = ($NEXT::NEXT{$key,$wanted_method}, $NEXT::SEEN); unless ($NEXT::NEXT{$key,$wanted_method}) { my @forebears = NEXT::ELSEWHERE::ancestors ref $self || $self, $wanted_class; while (@forebears) { last if shift @forebears eq $caller_class } no strict 'refs'; # Use *{"..."} when first accessing the CODE slot, to make sure # any typeglob stub is upgraded to a full typeglob. @{$NEXT::NEXT{$key,$wanted_method}} = map { my $stash = \%{"${_}::"}; ($stash->{$caller_method} && (*{"${_}::$caller_method"}{CODE})) ? *{$stash->{$caller_method}}{CODE} : () } @forebears unless $wanted_method eq 'AUTOLOAD'; @{$NEXT::NEXT{$key,$wanted_method}} = map { my $stash = \%{"${_}::"}; ($stash->{AUTOLOAD} && (*{"${_}::AUTOLOAD"}{CODE})) ? "${_}::AUTOLOAD" : () } @forebears unless @{$NEXT::NEXT{$key,$wanted_method}||[]}; $NEXT::SEEN->{$key,*{$caller}{CODE}}++; } my $call_method = shift @{$NEXT::NEXT{$key,$wanted_method}}; while (do { $wanted_class =~ /^NEXT\b.*\b(UNSEEN|DISTINCT)\b/ } && defined $call_method && $NEXT::SEEN->{$key,$call_method}++) { $call_method = shift @{$NEXT::NEXT{$key,$wanted_method}}; } unless (defined $call_method) { return unless do { $wanted_class =~ /^NEXT:.*:ACTUAL/ }; (local $Carp::CarpLevel)++; croak qq(Can't locate object method "$wanted_method" ), qq(via package "$caller_class"); }; return $self->$call_method(@_[1..$#_]) if ref $call_method eq 'CODE'; no strict 'refs'; do { ($wanted_method=${$caller_class."::AUTOLOAD"}) =~ s/.*::// } if $wanted_method eq 'AUTOLOAD'; $$call_method = $caller_class."::NEXT::".$wanted_method; return $call_method->(@_); }; } no strict 'vars'; package NEXT; NEXT::ELSEWHERE::buildAUTOLOAD(); package NEXT::UNSEEN; @ISA = 'NEXT'; NEXT::ELSEWHERE::buildAUTOLOAD(); package NEXT::DISTINCT; @ISA = 'NEXT'; NEXT::ELSEWHERE::buildAUTOLOAD(); package NEXT::ACTUAL; @ISA = 'NEXT'; NEXT::ELSEWHERE::buildAUTOLOAD(); package NEXT::ACTUAL::UNSEEN; @ISA = 'NEXT'; NEXT::ELSEWHERE::buildAUTOLOAD(); package NEXT::ACTUAL::DISTINCT; @ISA = 'NEXT'; NEXT::ELSEWHERE::buildAUTOLOAD(); package NEXT::UNSEEN::ACTUAL; @ISA = 'NEXT'; NEXT::ELSEWHERE::buildAUTOLOAD(); package NEXT::DISTINCT::ACTUAL; @ISA = 'NEXT'; NEXT::ELSEWHERE::buildAUTOLOAD(); package EVERY; sub EVERY::ELSEWHERE::buildAUTOLOAD { my $autoload_name = caller() . '::AUTOLOAD'; no strict 'refs'; *{$autoload_name} = sub { my ($self) = @_; my $depth = 1; until (((caller($depth))[3]||q{}) !~ /^\(eval\)$/) { $depth++ } my $caller = (caller($depth))[3]; my $wanted = $EVERY::AUTOLOAD || $autoload_name; undef $EVERY::AUTOLOAD; my ($wanted_class, $wanted_method) = do { $wanted =~ m{(.*)::(.*)}g }; my $key = ref($self) && overload::Overloaded($self) ? overload::StrVal($self) : $self; local $NEXT::ALREADY_IN_EVERY{$key,$wanted_method} = $NEXT::ALREADY_IN_EVERY{$key,$wanted_method}; return if $NEXT::ALREADY_IN_EVERY{$key,$wanted_method}++; my @forebears = NEXT::ELSEWHERE::ordered_ancestors ref $self || $self, $wanted_class; @forebears = reverse @forebears if do { $wanted_class =~ /\bLAST\b/ }; no strict 'refs'; my %seen; my @every = map { my $sub = "${_}::$wanted_method"; !*{$sub}{CODE} || $seen{$sub}++ ? () : $sub } @forebears unless $wanted_method eq 'AUTOLOAD'; my $want = wantarray; if (@every) { if ($want) { return map {($_, [$self->$_(@_[1..$#_])])} @every; } elsif (defined $want) { return { map {($_, scalar($self->$_(@_[1..$#_])))} @every }; } else { $self->$_(@_[1..$#_]) for @every; return; } } @every = map { my $sub = "${_}::AUTOLOAD"; !*{$sub}{CODE} || $seen{$sub}++ ? () : "${_}::AUTOLOAD" } @forebears; if ($want) { return map { $$_ = ref($self)."::EVERY::".$wanted_method; ($_, [$self->$_(@_[1..$#_])]); } @every; } elsif (defined $want) { return { map { $$_ = ref($self)."::EVERY::".$wanted_method; ($_, scalar($self->$_(@_[1..$#_]))) } @every }; } else { for (@every) { $$_ = ref($self)."::EVERY::".$wanted_method; $self->$_(@_[1..$#_]); } return; } }; } package EVERY::LAST; @ISA = 'EVERY'; EVERY::ELSEWHERE::buildAUTOLOAD(); package EVERY; @ISA = 'NEXT'; EVERY::ELSEWHERE::buildAUTOLOAD(); 1; __END__ # -*- Mode: cperl; coding: utf-8; cperl-indent-level: 4 -*- # vim: ts=4 sts=4 sw=4: use strict; package CPAN; $CPAN::VERSION = '2.20'; $CPAN::VERSION =~ s/_//; # we need to run chdir all over and we would get at wrong libraries # there use File::Spec (); BEGIN { if (File::Spec->can("rel2abs")) { for my $inc (@INC) { $inc = File::Spec->rel2abs($inc) unless ref $inc; } } $SIG{WINCH} = 'IGNORE' if exists $SIG{WINCH}; } use CPAN::Author; use CPAN::HandleConfig; use CPAN::Version; use CPAN::Bundle; use CPAN::CacheMgr; use CPAN::Complete; use CPAN::Debug; use CPAN::Distribution; use CPAN::Distrostatus; use CPAN::FTP; use CPAN::Index 1.93; # https://rt.cpan.org/Ticket/Display.html?id=43349 use CPAN::InfoObj; use CPAN::Module; use CPAN::Prompt; use CPAN::URL; use CPAN::Queue; use CPAN::Tarzip; use CPAN::DeferredCode; use CPAN::Shell; use CPAN::LWP::UserAgent; use CPAN::Exception::RecursiveDependency; use CPAN::Exception::yaml_not_installed; use CPAN::Exception::yaml_process_error; use Carp (); use Config (); use Cwd qw(chdir); use DirHandle (); use Exporter (); use ExtUtils::MakeMaker qw(prompt); # for some unknown reason, # 5.005_04 does not work without # this use File::Basename (); use File::Copy (); use File::Find; use File::Path (); use FileHandle (); use Fcntl qw(:flock); use Safe (); use Sys::Hostname qw(hostname); use Text::ParseWords (); use Text::Wrap (); # protect against "called too early" sub find_perl (); sub anycwd (); sub _uniq; no lib "."; require Mac::BuildTools if $^O eq 'MacOS'; if ($ENV{PERL5_CPAN_IS_RUNNING} && $$ != $ENV{PERL5_CPAN_IS_RUNNING}) { $ENV{PERL5_CPAN_IS_RUNNING_IN_RECURSION} ||= $ENV{PERL5_CPAN_IS_RUNNING}; my @rec = _uniq split(/,/, $ENV{PERL5_CPAN_IS_RUNNING_IN_RECURSION}), $$; $ENV{PERL5_CPAN_IS_RUNNING_IN_RECURSION} = join ",", @rec; # warn "# Note: Recursive call of CPAN.pm detected\n"; my $w = sprintf "# Note: CPAN.pm is running in process %d now", pop @rec; my %sleep = ( 5 => 30, 6 => 60, 7 => 120, ); my $sleep = @rec > 7 ? 300 : ($sleep{scalar @rec}||0); my $verbose = @rec >= 4; while (@rec) { $w .= sprintf " which has been called by process %d", pop @rec; } if ($sleep) { $w .= ".\n\n# Sleeping $sleep seconds to protect other processes\n"; } if ($verbose) { warn $w; } local $| = 1; while ($sleep > 0) { printf "\r#%5d", --$sleep; sleep 1; } print "\n"; } $ENV{PERL5_CPAN_IS_RUNNING}=$$; $ENV{PERL5_CPANPLUS_IS_RUNNING}=$$; # https://rt.cpan.org/Ticket/Display.html?id=23735 END { $CPAN::End++; &cleanup; } $CPAN::Signal ||= 0; $CPAN::Frontend ||= "CPAN::Shell"; unless (@CPAN::Defaultsites) { @CPAN::Defaultsites = map { CPAN::URL->new(TEXT => $_, FROM => "DEF") } "http://www.perl.org/CPAN/", "ftp://ftp.perl.org/pub/CPAN/"; } # $CPAN::iCwd (i for initial) $CPAN::iCwd ||= CPAN::anycwd(); $CPAN::Perl ||= CPAN::find_perl(); $CPAN::Defaultdocs ||= "http://search.cpan.org/perldoc?"; $CPAN::Defaultrecent ||= "http://search.cpan.org/uploads.rdf"; $CPAN::Defaultrecent ||= "http://cpan.uwinnipeg.ca/htdocs/cpan.xml"; # our globals are getting a mess use vars qw( $AUTOLOAD $Be_Silent $CONFIG_DIRTY $Defaultdocs $Echo_readline $Frontend $GOTOSHELL $HAS_USABLE $Have_warned $MAX_RECURSION $META $RUN_DEGRADED $Signal $SQLite $Suppress_readline $VERSION $autoload_recursion $term @Defaultsites @EXPORT ); $MAX_RECURSION = 32; @CPAN::ISA = qw(CPAN::Debug Exporter); # note that these functions live in CPAN::Shell and get executed via # AUTOLOAD when called directly @EXPORT = qw( autobundle bundle clean cvs_import expand force fforce get install install_tested is_tested make mkmyconfig notest perldoc readme recent recompile report shell smoke test upgrade ); sub soft_chdir_with_alternatives ($); { $autoload_recursion ||= 0; #-> sub CPAN::AUTOLOAD ; sub AUTOLOAD { ## no critic $autoload_recursion++; my($l) = $AUTOLOAD; $l =~ s/.*:://; if ($CPAN::Signal) { warn "Refusing to autoload '$l' while signal pending"; $autoload_recursion--; return; } if ($autoload_recursion > 1) { my $fullcommand = join " ", map { "'$_'" } $l, @_; warn "Refusing to autoload $fullcommand in recursion\n"; $autoload_recursion--; return; } my(%export); @export{@EXPORT} = ''; CPAN::HandleConfig->load unless $CPAN::Config_loaded++; if (exists $export{$l}) { CPAN::Shell->$l(@_); } else { die(qq{Unknown CPAN command "$AUTOLOAD". }. qq{Type ? for help.\n}); } $autoload_recursion--; } } { my $x = *SAVEOUT; # avoid warning open($x,">&STDOUT") or die "dup failed"; my $redir = 0; sub _redirect(@) { #die if $redir; local $_; push(@_,undef); while(defined($_=shift)) { if (s/^\s*>//){ my ($m) = s/^>// ? ">" : ""; s/\s+//; $_=shift unless length; die "no dest" unless defined; open(STDOUT,">$m$_") or die "open:$_:$!\n"; $redir=1; } elsif ( s/^\s*\|\s*// ) { my $pipe="| $_"; while(defined($_[0])){ $pipe .= ' ' . shift; } open(STDOUT,$pipe) or die "open:$pipe:$!\n"; $redir=1; } else { push(@_,$_); } } return @_; } sub _unredirect { return unless $redir; $redir = 0; ## redirect: unredirect and propagate errors. explicit close to wait for pipe. close(STDOUT); open(STDOUT,">&SAVEOUT"); die "$@" if "$@"; ## redirect: done } } sub _uniq { my(@list) = @_; my %seen; return grep { !$seen{$_}++ } @list; } #-> sub CPAN::shell ; sub shell { my($self) = @_; $Suppress_readline = ! -t STDIN unless defined $Suppress_readline; CPAN::HandleConfig->load unless $CPAN::Config_loaded++; my $oprompt = shift || CPAN::Prompt->new; my $prompt = $oprompt; my $commandline = shift || ""; $CPAN::CurrentCommandId ||= 1; local($^W) = 1; unless ($Suppress_readline) { require Term::ReadLine; if (! $term or $term->ReadLine eq "Term::ReadLine::Stub" ) { $term = Term::ReadLine->new('CPAN Monitor'); } if ($term->ReadLine eq "Term::ReadLine::Gnu") { my $attribs = $term->Attribs; $attribs->{attempted_completion_function} = sub { &CPAN::Complete::gnu_cpl; } } else { $readline::rl_completion_function = $readline::rl_completion_function = 'CPAN::Complete::cpl'; } if (my $histfile = $CPAN::Config->{'histfile'}) {{ unless ($term->can("AddHistory")) { $CPAN::Frontend->mywarn("Terminal does not support AddHistory.\n"); last; } $META->readhist($term,$histfile); }} for ($CPAN::Config->{term_ornaments}) { # alias local $Term::ReadLine::termcap_nowarn = 1; $term->ornaments($_) if defined; } # $term->OUT is autoflushed anyway my $odef = select STDERR; $| = 1; select STDOUT; $| = 1; select $odef; } $META->checklock(); my @cwd = grep { defined $_ and length $_ } CPAN::anycwd(), File::Spec->can("tmpdir") ? File::Spec->tmpdir() : (), File::Spec->rootdir(); my $try_detect_readline; $try_detect_readline = $term->ReadLine eq "Term::ReadLine::Stub" if $term; unless ($CPAN::Config->{inhibit_startup_message}) { my $rl_avail = $Suppress_readline ? "suppressed" : ($term->ReadLine ne "Term::ReadLine::Stub") ? "enabled" : "available (maybe install Bundle::CPAN or Bundle::CPANxxl?)"; $CPAN::Frontend->myprint( sprintf qq{ cpan shell -- CPAN exploration and modules installation (v%s) Enter 'h' for help. }, $CPAN::VERSION, ) } my($continuation) = ""; my $last_term_ornaments; SHELLCOMMAND: while () { if ($Suppress_readline) { if ($Echo_readline) { $|=1; } print $prompt; last SHELLCOMMAND unless defined ($_ = <> ); if ($Echo_readline) { # backdoor: I could not find a way to record sessions print $_; } chomp; } else { last SHELLCOMMAND unless defined ($_ = $term->readline($prompt, $commandline)); } $_ = "$continuation$_" if $continuation; s/^\s+//; next SHELLCOMMAND if /^$/; s/^\s*\?\s*/help /; if (/^(?:q(?:uit)?|bye|exit)\s*$/i) { last SHELLCOMMAND; } elsif (s/\\$//s) { chomp; $continuation = $_; $prompt = " > "; } elsif (/^\!/) { s/^\!//; my($eval) = $_; package CPAN::Eval; # hide from the indexer use strict; use vars qw($import_done); CPAN->import(':DEFAULT') unless $import_done++; CPAN->debug("eval[$eval]") if $CPAN::DEBUG; eval($eval); warn $@ if $@; $continuation = ""; $prompt = $oprompt; } elsif (/./) { my(@line); eval { @line = Text::ParseWords::shellwords($_) }; warn($@), next SHELLCOMMAND if $@; warn("Text::Parsewords could not parse the line [$_]"), next SHELLCOMMAND unless @line; $CPAN::META->debug("line[".join("|",@line)."]") if $CPAN::DEBUG; my $command = shift @line; eval { local (*STDOUT)=*STDOUT; @line = _redirect(@line); CPAN::Shell->$command(@line) }; my $command_error = $@; _unredirect; my $reported_error; if ($command_error) { my $err = $command_error; if (ref $err and $err->isa('CPAN::Exception::blocked_urllist')) { $CPAN::Frontend->mywarn("Client not fully configured, please proceed with configuring.$err"); $reported_error = ref $err; } else { # I'd prefer never to arrive here and make all errors exception objects if ($err =~ /\S/) { require Carp; require Dumpvalue; my $dv = Dumpvalue->new(tick => '"'); Carp::cluck(sprintf "Catching error: %s", $dv->stringify($err)); } } } if ($command =~ /^( # classic commands make |test |install |clean # pragmas for classic commands |ff?orce |notest # compounds |report |smoke |upgrade )$/x) { # only commands that tell us something about failed distros # eval necessary for people without an urllist eval {CPAN::Shell->failed($CPAN::CurrentCommandId,1);}; if (my $err = $@) { unless (ref $err and $reported_error eq ref $err) { die $@; } } } soft_chdir_with_alternatives(\@cwd); $CPAN::Frontend->myprint("\n"); $continuation = ""; $CPAN::CurrentCommandId++; $prompt = $oprompt; } } continue { $commandline = ""; # I do want to be able to pass a default to # shell, but on the second command I see no # use in that $Signal=0; CPAN::Queue->nullify_queue; if ($try_detect_readline) { if ($CPAN::META->has_inst("Term::ReadLine::Gnu") || $CPAN::META->has_inst("Term::ReadLine::Perl") ) { delete $INC{"Term/ReadLine.pm"}; my $redef = 0; local($SIG{__WARN__}) = CPAN::Shell::paintdots_onreload(\$redef); require Term::ReadLine; $CPAN::Frontend->myprint("\n$redef subroutines in ". "Term::ReadLine redefined\n"); $GOTOSHELL = 1; } } if ($term and $term->can("ornaments")) { for ($CPAN::Config->{term_ornaments}) { # alias if (defined $_) { if (not defined $last_term_ornaments or $_ != $last_term_ornaments ) { local $Term::ReadLine::termcap_nowarn = 1; $term->ornaments($_); $last_term_ornaments = $_; } } else { undef $last_term_ornaments; } } } for my $class (qw(Module Distribution)) { # again unsafe meta access? for my $dm (sort keys %{$CPAN::META->{readwrite}{"CPAN::$class"}}) { next unless $CPAN::META->{readwrite}{"CPAN::$class"}{$dm}{incommandcolor}; CPAN->debug("BUG: $class '$dm' was in command state, resetting"); delete $CPAN::META->{readwrite}{"CPAN::$class"}{$dm}{incommandcolor}; } } if ($GOTOSHELL) { $GOTOSHELL = 0; # not too often $META->savehist if $CPAN::term && $CPAN::term->can("GetHistory"); @_ = ($oprompt,""); goto &shell; } } soft_chdir_with_alternatives(\@cwd); } #-> CPAN::soft_chdir_with_alternatives ; sub soft_chdir_with_alternatives ($) { my($cwd) = @_; unless (@$cwd) { my $root = File::Spec->rootdir(); $CPAN::Frontend->mywarn(qq{Warning: no good directory to chdir to! Trying '$root' as temporary haven. }); push @$cwd, $root; } while () { if (chdir $cwd->[0]) { return; } else { if (@$cwd>1) { $CPAN::Frontend->mywarn(qq{Could not chdir to "$cwd->[0]": $! Trying to chdir to "$cwd->[1]" instead. }); shift @$cwd; } else { $CPAN::Frontend->mydie(qq{Could not chdir to "$cwd->[0]": $!}); } } } } sub _flock { my($fh,$mode) = @_; if ( $Config::Config{d_flock} || $Config::Config{d_fcntl_can_lock} ) { return flock $fh, $mode; } elsif (!$Have_warned->{"d_flock"}++) { $CPAN::Frontend->mywarn("Your OS does not seem to support locking; continuing and ignoring all locking issues\n"); $CPAN::Frontend->mysleep(5); return 1; } else { return 1; } } sub _yaml_module () { my $yaml_module = $CPAN::Config->{yaml_module} || "YAML"; if ( $yaml_module ne "YAML" && !$CPAN::META->has_inst($yaml_module) ) { # $CPAN::Frontend->mywarn("'$yaml_module' not installed, falling back to 'YAML'\n"); $yaml_module = "YAML"; } if ($yaml_module eq "YAML" && $CPAN::META->has_inst($yaml_module) && $YAML::VERSION < 0.60 && !$Have_warned->{"YAML"}++ ) { $CPAN::Frontend->mywarn("Warning: YAML version '$YAML::VERSION' is too low, please upgrade!\n". "I'll continue but problems are *very* likely to happen.\n" ); $CPAN::Frontend->mysleep(5); } return $yaml_module; } # CPAN::_yaml_loadfile sub _yaml_loadfile { my($self,$local_file) = @_; return +[] unless -s $local_file; my $yaml_module = _yaml_module; if ($CPAN::META->has_inst($yaml_module)) { # temporarily enable yaml code deserialisation no strict 'refs'; # 5.6.2 could not do the local() with the reference # so we do it manually instead my $old_loadcode = ${"$yaml_module\::LoadCode"}; ${ "$yaml_module\::LoadCode" } = $CPAN::Config->{yaml_load_code} || 0; my ($code, @yaml); if ($code = UNIVERSAL::can($yaml_module, "LoadFile")) { eval { @yaml = $code->($local_file); }; if ($@) { # this shall not be done by the frontend die CPAN::Exception::yaml_process_error->new($yaml_module,$local_file,"parse",$@); } } elsif ($code = UNIVERSAL::can($yaml_module, "Load")) { local *FH; unless (open FH, $local_file) { $CPAN::Frontend->mywarn("Could not open '$local_file': $!"); return +[]; } local $/; my $ystream = ; eval { @yaml = $code->($ystream); }; if ($@) { # this shall not be done by the frontend die CPAN::Exception::yaml_process_error->new($yaml_module,$local_file,"parse",$@); } } ${"$yaml_module\::LoadCode"} = $old_loadcode; return \@yaml; } else { # this shall not be done by the frontend die CPAN::Exception::yaml_not_installed->new($yaml_module, $local_file, "parse"); } return +[]; } # CPAN::_yaml_dumpfile sub _yaml_dumpfile { my($self,$local_file,@what) = @_; my $yaml_module = _yaml_module; if ($CPAN::META->has_inst($yaml_module)) { my $code; if (UNIVERSAL::isa($local_file, "FileHandle")) { $code = UNIVERSAL::can($yaml_module, "Dump"); eval { print $local_file $code->(@what) }; } elsif ($code = UNIVERSAL::can($yaml_module, "DumpFile")) { eval { $code->($local_file,@what); }; } elsif ($code = UNIVERSAL::can($yaml_module, "Dump")) { local *FH; open FH, ">$local_file" or die "Could not open '$local_file': $!"; print FH $code->(@what); } if ($@) { die CPAN::Exception::yaml_process_error->new($yaml_module,$local_file,"dump",$@); } } else { if (UNIVERSAL::isa($local_file, "FileHandle")) { # I think this case does not justify a warning at all } else { die CPAN::Exception::yaml_not_installed->new($yaml_module, $local_file, "dump"); } } } sub _init_sqlite () { unless ($CPAN::META->has_inst("CPAN::SQLite")) { $CPAN::Frontend->mywarn(qq{CPAN::SQLite not installed, trying to work without\n}) unless $Have_warned->{"CPAN::SQLite"}++; return; } require CPAN::SQLite::META; # not needed since CVS version of 2006-12-17 $CPAN::SQLite ||= CPAN::SQLite::META->new($CPAN::META); } { my $negative_cache = {}; sub _sqlite_running { if ($negative_cache->{time} && time < $negative_cache->{time} + 60) { # need to cache the result, otherwise too slow return $negative_cache->{fact}; } else { $negative_cache = {}; # reset } my $ret = $CPAN::Config->{use_sqlite} && ($CPAN::SQLite || _init_sqlite()); return $ret if $ret; # fast anyway $negative_cache->{time} = time; return $negative_cache->{fact} = $ret; } } $META ||= CPAN->new; # In case we re-eval ourselves we need the || # from here on only subs. ################################################################################ sub _perl_fingerprint { my($self,$other_fingerprint) = @_; my $dll = eval {OS2::DLLname()}; my $mtime_dll = 0; if (defined $dll) { $mtime_dll = (-f $dll ? (stat(_))[9] : '-1'); } my $mtime_perl = (-f CPAN::find_perl ? (stat(_))[9] : '-1'); my $this_fingerprint = { '$^X' => CPAN::find_perl, sitearchexp => $Config::Config{sitearchexp}, 'mtime_$^X' => $mtime_perl, 'mtime_dll' => $mtime_dll, }; if ($other_fingerprint) { if (exists $other_fingerprint->{'stat($^X)'}) { # repair fp from rev. 1.88_57 $other_fingerprint->{'mtime_$^X'} = $other_fingerprint->{'stat($^X)'}[9]; } # mandatory keys since 1.88_57 for my $key (qw($^X sitearchexp mtime_dll mtime_$^X)) { return unless $other_fingerprint->{$key} eq $this_fingerprint->{$key}; } return 1; } else { return $this_fingerprint; } } sub suggest_myconfig () { SUGGEST_MYCONFIG: if(!$INC{'CPAN/MyConfig.pm'}) { $CPAN::Frontend->myprint("You don't seem to have a user ". "configuration (MyConfig.pm) yet.\n"); my $new = CPAN::Shell::colorable_makemaker_prompt("Do you want to create a ". "user configuration now? (Y/n)", "yes"); if($new =~ m{^y}i) { CPAN::Shell->mkmyconfig(); return &checklock; } else { $CPAN::Frontend->mydie("OK, giving up."); } } } #-> sub CPAN::all_objects ; sub all_objects { my($mgr,$class) = @_; CPAN::HandleConfig->load unless $CPAN::Config_loaded++; CPAN->debug("mgr[$mgr] class[$class]") if $CPAN::DEBUG; CPAN::Index->reload; values %{ $META->{readwrite}{$class} }; # unsafe meta access, ok } # Called by shell, not in batch mode. In batch mode I see no risk in # having many processes updating something as installations are # continually checked at runtime. In shell mode I suspect it is # unintentional to open more than one shell at a time #-> sub CPAN::checklock ; sub checklock { my($self) = @_; my $lockfile = File::Spec->catfile($CPAN::Config->{cpan_home},".lock"); if (-f $lockfile && -M _ > 0) { my $fh = FileHandle->new($lockfile) or $CPAN::Frontend->mydie("Could not open lockfile '$lockfile': $!"); my $otherpid = <$fh>; my $otherhost = <$fh>; $fh->close; if (defined $otherpid && length $otherpid) { chomp $otherpid; } if (defined $otherhost && length $otherhost) { chomp $otherhost; } my $thishost = hostname(); my $ask_if_degraded_wanted = 0; if (defined $otherhost && defined $thishost && $otherhost ne '' && $thishost ne '' && $otherhost ne $thishost) { $CPAN::Frontend->mydie(sprintf("CPAN.pm panic: Lockfile '$lockfile'\n". "reports other host $otherhost and other ". "process $otherpid.\n". "Cannot proceed.\n")); } elsif ($RUN_DEGRADED) { $CPAN::Frontend->mywarn("Running in downgraded mode (experimental)\n"); } elsif (defined $otherpid && $otherpid) { return if $$ == $otherpid; # should never happen $CPAN::Frontend->mywarn( qq{ There seems to be running another CPAN process (pid $otherpid). Contacting... }); if (kill 0, $otherpid or $!{EPERM}) { $CPAN::Frontend->mywarn(qq{Other job is running.\n}); $ask_if_degraded_wanted = 1; } elsif (-w $lockfile) { my($ans) = CPAN::Shell::colorable_makemaker_prompt (qq{Other job not responding. Shall I overwrite }. qq{the lockfile '$lockfile'? (Y/n)},"y"); $CPAN::Frontend->myexit("Ok, bye\n") unless $ans =~ /^y/i; } else { Carp::croak( qq{Lockfile '$lockfile' not writable by you. }. qq{Cannot proceed.\n}. qq{ On UNIX try:\n}. qq{ rm '$lockfile'\n}. qq{ and then rerun us.\n} ); } } elsif ($^O eq "MSWin32") { $CPAN::Frontend->mywarn( qq{ There seems to be running another CPAN process according to '$lockfile'. }); $ask_if_degraded_wanted = 1; } else { $CPAN::Frontend->mydie(sprintf("CPAN.pm panic: Found invalid lockfile ". "'$lockfile', please remove. Cannot proceed.\n")); } if ($ask_if_degraded_wanted) { my($ans) = CPAN::Shell::colorable_makemaker_prompt (qq{Shall I try to run in downgraded }. qq{mode? (Y/n)},"y"); if ($ans =~ /^y/i) { $CPAN::Frontend->mywarn("Running in downgraded mode (experimental). Please report if something unexpected happens\n"); $RUN_DEGRADED = 1; for ($CPAN::Config) { # XXX # $_->{build_dir_reuse} = 0; # 2006-11-17 akoenig Why was that? $_->{commandnumber_in_prompt} = 0; # visibility $_->{histfile} = ""; # who should win otherwise? $_->{cache_metadata} = 0; # better would be a lock? $_->{use_sqlite} = 0; # better would be a write lock! $_->{auto_commit} = 0; # we are violent, do not persist $_->{test_report} = 0; # Oliver Paukstadt had sent wrong reports in degraded mode } } else { my $msg = "You may want to kill the other job and delete the lockfile."; if (defined $otherpid) { $msg .= " Something like: kill $otherpid rm $lockfile "; } $CPAN::Frontend->mydie("\n$msg"); } } } my $dotcpan = $CPAN::Config->{cpan_home}; eval { File::Path::mkpath($dotcpan);}; if ($@) { # A special case at least for Jarkko. my $firsterror = $@; my $seconderror; my $symlinkcpan; if (-l $dotcpan) { $symlinkcpan = readlink $dotcpan; die "readlink $dotcpan failed: $!" unless defined $symlinkcpan; eval { File::Path::mkpath($symlinkcpan); }; if ($@) { $seconderror = $@; } else { $CPAN::Frontend->mywarn(qq{ Working directory $symlinkcpan created. }); } } unless (-d $dotcpan) { my $mess = qq{ Your configuration suggests "$dotcpan" as your CPAN.pm working directory. I could not create this directory due to this error: $firsterror\n}; $mess .= qq{ As "$dotcpan" is a symlink to "$symlinkcpan", I tried to create that, but I failed with this error: $seconderror } if $seconderror; $mess .= qq{ Please make sure the directory exists and is writable. }; $CPAN::Frontend->mywarn($mess); return suggest_myconfig; } } # $@ after eval mkpath $dotcpan if (0) { # to test what happens when a race condition occurs for (reverse 1..10) { print $_, "\n"; sleep 1; } } # locking if (!$RUN_DEGRADED && !$self->{LOCKFH}) { my $fh; unless ($fh = FileHandle->new("+>>$lockfile")) { $CPAN::Frontend->mywarn(qq{ Your configuration suggests that CPAN.pm should use a working directory of $CPAN::Config->{cpan_home} Unfortunately we could not create the lock file $lockfile due to '$!'. Please make sure that the configuration variable \$CPAN::Config->{cpan_home} points to a directory where you can write a .lock file. You can set this variable in either a CPAN/MyConfig.pm or a CPAN/Config.pm in your \@INC path; }); return suggest_myconfig; } my $sleep = 1; while (!CPAN::_flock($fh, LOCK_EX|LOCK_NB)) { my $err = $! || "unknown error"; if ($sleep>3) { $CPAN::Frontend->mydie("Could not lock '$lockfile' with flock: $err; giving up\n"); } $CPAN::Frontend->mysleep($sleep+=0.1); $CPAN::Frontend->mywarn("Could not lock '$lockfile' with flock: $err; retrying\n"); } seek $fh, 0, 0; truncate $fh, 0; $fh->autoflush(1); $fh->print($$, "\n"); $fh->print(hostname(), "\n"); $self->{LOCK} = $lockfile; $self->{LOCKFH} = $fh; } $SIG{TERM} = sub { my $sig = shift; &cleanup; $CPAN::Frontend->mydie("Got SIG$sig, leaving"); }; $SIG{INT} = sub { # no blocks!!! my $sig = shift; &cleanup if $Signal; die "Got yet another signal" if $Signal > 1; $CPAN::Frontend->mydie("Got another SIG$sig") if $Signal; $CPAN::Frontend->mywarn("Caught SIG$sig, trying to continue\n"); $Signal++; }; # From: Larry Wall # Subject: Re: deprecating SIGDIE # To: perl5-porters@perl.org # Date: Thu, 30 Sep 1999 14:58:40 -0700 (PDT) # # The original intent of __DIE__ was only to allow you to substitute one # kind of death for another on an application-wide basis without respect # to whether you were in an eval or not. As a global backstop, it should # not be used any more lightly (or any more heavily :-) than class # UNIVERSAL. Any attempt to build a general exception model on it should # be politely squashed. Any bug that causes every eval {} to have to be # modified should be not so politely squashed. # # Those are my current opinions. It is also my opinion that polite # arguments degenerate to personal arguments far too frequently, and that # when they do, it's because both people wanted it to, or at least didn't # sufficiently want it not to. # # Larry # global backstop to cleanup if we should really die $SIG{__DIE__} = \&cleanup; $self->debug("Signal handler set.") if $CPAN::DEBUG; } #-> sub CPAN::DESTROY ; sub DESTROY { &cleanup; # need an eval? } #-> sub CPAN::anycwd ; sub anycwd () { my $getcwd; $getcwd = $CPAN::Config->{'getcwd'} || 'cwd'; CPAN->$getcwd(); } #-> sub CPAN::cwd ; sub cwd {Cwd::cwd();} #-> sub CPAN::getcwd ; sub getcwd {Cwd::getcwd();} #-> sub CPAN::fastcwd ; sub fastcwd {Cwd::fastcwd();} #-> sub CPAN::getdcwd ; sub getdcwd {Cwd::getdcwd();} #-> sub CPAN::backtickcwd ; sub backtickcwd {my $cwd = `cwd`; chomp $cwd; $cwd} # Adapted from Probe::Perl #-> sub CPAN::_perl_is_same sub _perl_is_same { my ($perl) = @_; return MM->maybe_command($perl) && `$perl -MConfig=myconfig -e print -e myconfig` eq Config->myconfig; } # Adapted in part from Probe::Perl #-> sub CPAN::find_perl ; sub find_perl () { if ( File::Spec->file_name_is_absolute($^X) ) { return $^X; } else { my $exe = $Config::Config{exe_ext}; my @candidates = ( File::Spec->catfile($CPAN::iCwd,$^X), $Config::Config{'perlpath'}, ); for my $perl_name ($^X, 'perl', 'perl5', "perl$]") { for my $path (File::Spec->path(), $Config::Config{'binexp'}) { if ( defined($path) && length $path && -d $path ) { my $perl = File::Spec->catfile($path,$perl_name); push @candidates, $perl; # try with extension if not provided already if ($^O eq 'VMS') { # VMS might have a file version at the end push @candidates, $perl . $exe unless $perl =~ m/$exe(;\d+)?$/i; } elsif (defined $exe && length $exe) { push @candidates, $perl . $exe unless $perl =~ m/$exe$/i; } } } } for my $perl ( @candidates ) { if (MM->maybe_command($perl) && _perl_is_same($perl)) { $^X = $perl; return $perl; } } } return $^X; # default fall back } #-> sub CPAN::exists ; sub exists { my($mgr,$class,$id) = @_; CPAN::HandleConfig->load unless $CPAN::Config_loaded++; CPAN::Index->reload; ### Carp::croak "exists called without class argument" unless $class; $id ||= ""; $id =~ s/:+/::/g if $class eq "CPAN::Module"; my $exists; if (CPAN::_sqlite_running) { $exists = (exists $META->{readonly}{$class}{$id} or $CPAN::SQLite->set($class, $id)); } else { $exists = exists $META->{readonly}{$class}{$id}; } $exists ||= exists $META->{readwrite}{$class}{$id}; # unsafe meta access, ok } #-> sub CPAN::delete ; sub delete { my($mgr,$class,$id) = @_; delete $META->{readonly}{$class}{$id}; # unsafe meta access, ok delete $META->{readwrite}{$class}{$id}; # unsafe meta access, ok } #-> sub CPAN::has_usable # has_inst is sometimes too optimistic, we should replace it with this # has_usable whenever a case is given sub has_usable { my($self,$mod,$message) = @_; return 1 if $HAS_USABLE->{$mod}; my $has_inst = $self->has_inst($mod,$message); return unless $has_inst; my $usable; $usable = { # # these subroutines die if they believe the installed version is unusable; # 'CPAN::Meta' => [ sub { require CPAN::Meta; unless (CPAN::Version->vge(CPAN::Meta->VERSION, 2.110350)) { for ("Will not use CPAN::Meta, need version 2.110350\n") { $CPAN::Frontend->mywarn($_); die $_; } } }, ], 'CPAN::Meta::Requirements' => [ sub { if (defined $CPAN::Meta::Requirements::VERSION && CPAN::Version->vlt($CPAN::Meta::Requirements::VERSION, "2.120920") ) { delete $INC{"CPAN/Meta/Requirements.pm"}; } require CPAN::Meta::Requirements; unless (CPAN::Version->vge(CPAN::Meta::Requirements->VERSION, 2.120920)) { for ("Will not use CPAN::Meta::Requirements, need version 2.120920\n") { $CPAN::Frontend->mywarn($_); die $_; } } }, ], LWP => [ # we frequently had "Can't locate object # method "new" via package "LWP::UserAgent" at # (eval 69) line 2006 sub {require LWP}, sub {require LWP::UserAgent}, sub {require HTTP::Request}, sub {require URI::URL; unless (CPAN::Version->vge(URI::URL::->VERSION,0.08)) { for ("Will not use URI::URL, need 0.08\n") { $CPAN::Frontend->mywarn($_); die $_; } } }, ], 'Net::FTP' => [ sub { my $var = $CPAN::Config->{ftp_proxy} || $ENV{ftp_proxy}; if ($var and $var =~ /^http:/i) { # rt #110833 for ("Net::FTP cannot handle http proxy") { $CPAN::Frontend->mywarn($_); die $_; } } }, sub {require Net::FTP}, sub {require Net::Config}, ], 'HTTP::Tiny' => [ sub { require HTTP::Tiny; unless (CPAN::Version->vge(HTTP::Tiny->VERSION, 0.005)) { for ("Will not use HTTP::Tiny, need version 0.005\n") { $CPAN::Frontend->mywarn($_); die $_; } } }, ], 'File::HomeDir' => [ sub {require File::HomeDir; unless (CPAN::Version->vge(File::HomeDir::->VERSION, 0.52)) { for ("Will not use File::HomeDir, need 0.52\n") { $CPAN::Frontend->mywarn($_); die $_; } } }, ], 'Archive::Tar' => [ sub {require Archive::Tar; my $demand = "1.50"; unless (CPAN::Version->vge(Archive::Tar::->VERSION, $demand)) { my $atv = Archive::Tar->VERSION; for ("You have Archive::Tar $atv, but $demand or later is recommended. Please upgrade.\n") { $CPAN::Frontend->mywarn($_); # don't die, because we may need # Archive::Tar to upgrade } } }, ], 'File::Temp' => [ # XXX we should probably delete from # %INC too so we can load after we # installed a new enough version -- # I'm not sure. sub {require File::Temp; unless (CPAN::Version->vge(File::Temp::->VERSION,0.16)) { for ("Will not use File::Temp, need 0.16\n") { $CPAN::Frontend->mywarn($_); die $_; } } }, ] }; if ($usable->{$mod}) { local @INC = @INC; pop @INC if $INC[-1] eq '.'; for my $c (0..$#{$usable->{$mod}}) { my $code = $usable->{$mod}[$c]; my $ret = eval { &$code() }; $ret = "" unless defined $ret; if ($@) { # warn "DEBUG: c[$c]\$\@[$@]ret[$ret]"; return; } } } return $HAS_USABLE->{$mod} = 1; } sub frontend { shift; $CPAN::Frontend = shift if @_; $CPAN::Frontend; } sub use_inst { my ($self, $module) = @_; unless ($self->has_inst($module)) { $self->frontend->mydie("$module not installed, cannot continue"); } } #-> sub CPAN::has_inst sub has_inst { my($self,$mod,$message) = @_; Carp::croak("CPAN->has_inst() called without an argument") unless defined $mod; my %dont = map { $_ => 1 } keys %{$CPAN::META->{dontload_hash}||{}}, keys %{$CPAN::Config->{dontload_hash}||{}}, @{$CPAN::Config->{dontload_list}||[]}; if (defined $message && $message eq "no" # as far as I remember only used by Nox || $dont{$mod} ) { $CPAN::META->{dontload_hash}{$mod}||=1; # unsafe meta access, ok return 0; } local @INC = @INC; pop @INC if $INC[-1] eq '.'; my $file = $mod; my $obj; $file =~ s|::|/|g; $file .= ".pm"; if ($INC{$file}) { # checking %INC is wrong, because $INC{LWP} may be true # although $INC{"URI/URL.pm"} may have failed. But as # I really want to say "blah loaded OK", I have to somehow # cache results. ### warn "$file in %INC"; #debug return 1; } elsif (eval { require $file }) { # eval is good: if we haven't yet read the database it's # perfect and if we have installed the module in the meantime, # it tries again. The second require is only a NOOP returning # 1 if we had success, otherwise it's retrying my $mtime = (stat $INC{$file})[9]; # privileged files loaded by has_inst; Note: we use $mtime # as a proxy for a checksum. $CPAN::Shell::reload->{$file} = $mtime; my $v = eval "\$$mod\::VERSION"; $v = $v ? " (v$v)" : ""; CPAN::Shell->optprint("load_module","CPAN: $mod loaded ok$v\n"); if ($mod eq "CPAN::WAIT") { push @CPAN::Shell::ISA, 'CPAN::WAIT'; } return 1; } elsif ($mod eq "Net::FTP") { $CPAN::Frontend->mywarn(qq{ Please, install Net::FTP as soon as possible. CPAN.pm installs it for you if you just type install Bundle::libnet }) unless $Have_warned->{"Net::FTP"}++; $CPAN::Frontend->mysleep(3); } elsif ($mod eq "Digest::SHA") { if ($Have_warned->{"Digest::SHA"}++) { $CPAN::Frontend->mywarn(qq{CPAN: checksum security checks disabled }. qq{because Digest::SHA not installed.\n}); } else { $CPAN::Frontend->mywarn(qq{ CPAN: checksum security checks disabled because Digest::SHA not installed. Please consider installing the Digest::SHA module. }); $CPAN::Frontend->mysleep(2); } } elsif ($mod eq "Module::Signature") { # NOT prefs_lookup, we are not a distro my $check_sigs = $CPAN::Config->{check_sigs}; if (not $check_sigs) { # they do not want us:-( } elsif (not $Have_warned->{"Module::Signature"}++) { # No point in complaining unless the user can # reasonably install and use it. if (eval { require Crypt::OpenPGP; 1 } || ( defined $CPAN::Config->{'gpg'} && $CPAN::Config->{'gpg'} =~ /\S/ ) ) { $CPAN::Frontend->mywarn(qq{ CPAN: Module::Signature security checks disabled because Module::Signature not installed. Please consider installing the Module::Signature module. You may also need to be able to connect over the Internet to the public key servers like pool.sks-keyservers.net or pgp.mit.edu. }); $CPAN::Frontend->mysleep(2); } } } else { delete $INC{$file}; # if it inc'd LWP but failed during, say, URI } return 0; } #-> sub CPAN::instance ; sub instance { my($mgr,$class,$id) = @_; CPAN::Index->reload; $id ||= ""; # unsafe meta access, ok? return $META->{readwrite}{$class}{$id} if exists $META->{readwrite}{$class}{$id}; $META->{readwrite}{$class}{$id} ||= $class->new(ID => $id); } #-> sub CPAN::new ; sub new { bless {}, shift; } #-> sub CPAN::_exit_messages ; sub _exit_messages { my ($self) = @_; $self->{exit_messages} ||= []; } #-> sub CPAN::cleanup ; sub cleanup { # warn "cleanup called with arg[@_] End[$CPAN::End] Signal[$Signal]"; local $SIG{__DIE__} = ''; my($message) = @_; my $i = 0; my $ineval = 0; my($subroutine); while ((undef,undef,undef,$subroutine) = caller(++$i)) { $ineval = 1, last if $subroutine eq '(eval)'; } return if $ineval && !$CPAN::End; return unless defined $META->{LOCK}; return unless -f $META->{LOCK}; $META->savehist; $META->{cachemgr} ||= CPAN::CacheMgr->new('atexit'); close $META->{LOCKFH}; unlink $META->{LOCK}; # require Carp; # Carp::cluck("DEBUGGING"); if ( $CPAN::CONFIG_DIRTY ) { $CPAN::Frontend->mywarn("Warning: Configuration not saved.\n"); } $CPAN::Frontend->myprint("Lockfile removed.\n"); for my $msg ( @{ $META->_exit_messages } ) { $CPAN::Frontend->myprint($msg); } } #-> sub CPAN::readhist sub readhist { my($self,$term,$histfile) = @_; my $histsize = $CPAN::Config->{'histsize'} || 100; $term->Attribs->{'MaxHistorySize'} = $histsize if (defined($term->Attribs->{'MaxHistorySize'})); my($fh) = FileHandle->new; open $fh, "<$histfile" or return; local $/ = "\n"; while (<$fh>) { chomp; $term->AddHistory($_); } close $fh; } #-> sub CPAN::savehist sub savehist { my($self) = @_; my($histfile,$histsize); unless ($histfile = $CPAN::Config->{'histfile'}) { $CPAN::Frontend->mywarn("No history written (no histfile specified).\n"); return; } $histsize = $CPAN::Config->{'histsize'} || 100; if ($CPAN::term) { unless ($CPAN::term->can("GetHistory")) { $CPAN::Frontend->mywarn("Terminal does not support GetHistory.\n"); return; } } else { return; } my @h = $CPAN::term->GetHistory; splice @h, 0, @h-$histsize if @h>$histsize; my($fh) = FileHandle->new; open $fh, ">$histfile" or $CPAN::Frontend->mydie("Couldn't open >$histfile: $!"); local $\ = local $, = "\n"; print $fh @h; close $fh; } #-> sub CPAN::is_tested sub is_tested { my($self,$what,$when) = @_; unless ($what) { Carp::cluck("DEBUG: empty what"); return; } $self->{is_tested}{$what} = $when; } #-> sub CPAN::reset_tested # forget all distributions tested -- resets what gets included in PERL5LIB sub reset_tested { my ($self) = @_; $self->{is_tested} = {}; } #-> sub CPAN::is_installed # unsets the is_tested flag: as soon as the thing is installed, it is # not needed in set_perl5lib anymore sub is_installed { my($self,$what) = @_; delete $self->{is_tested}{$what}; } sub _list_sorted_descending_is_tested { my($self) = @_; my $foul = 0; my @sorted = sort { ($self->{is_tested}{$b}||0) <=> ($self->{is_tested}{$a}||0) } grep { if ($foul){ 0 } elsif (-e) { 1 } else { $foul = $_; 0 } } keys %{$self->{is_tested}}; if ($foul) { $CPAN::Frontend->mywarn("Lost build_dir detected ($foul), giving up all cached test results of currently running session.\n"); for my $dbd (sort keys %{$self->{is_tested}}) { # distro-build-dir SEARCH: for my $d (sort { $a->id cmp $b->id } $CPAN::META->all_objects("CPAN::Distribution")) { if ($d->{build_dir} && $d->{build_dir} eq $dbd) { $CPAN::Frontend->mywarn(sprintf "Flushing cache for %s\n", $d->pretty_id); $d->fforce(""); last SEARCH; } } delete $self->{is_tested}{$dbd}; } return (); } else { return @sorted; } } #-> sub CPAN::set_perl5lib # Notes on max environment variable length: # - Win32 : XP or later, 8191; Win2000 or NT4, 2047 { my $fh; sub set_perl5lib { my($self,$for) = @_; unless ($for) { (undef,undef,undef,$for) = caller(1); $for =~ s/.*://; } $self->{is_tested} ||= {}; return unless %{$self->{is_tested}}; my $env = $ENV{PERL5LIB}; $env = $ENV{PERLLIB} unless defined $env; my @env; push @env, split /\Q$Config::Config{path_sep}\E/, $env if defined $env and length $env; #my @dirs = map {("$_/blib/arch", "$_/blib/lib")} keys %{$self->{is_tested}}; #$CPAN::Frontend->myprint("Prepending @dirs to PERL5LIB.\n"); my @dirs = map {("$_/blib/arch", "$_/blib/lib")} $self->_list_sorted_descending_is_tested; return if !@dirs; if (@dirs < 12) { $CPAN::Frontend->optprint('perl5lib', "Prepending @dirs to PERL5LIB for '$for'\n"); $ENV{PERL5LIB} = join $Config::Config{path_sep}, @dirs, @env; } elsif (@dirs < 24 ) { my @d = map {my $cp = $_; $cp =~ s/^\Q$CPAN::Config->{build_dir}\E/%BUILDDIR%/; $cp } @dirs; $CPAN::Frontend->optprint('perl5lib', "Prepending @d to PERL5LIB; ". "%BUILDDIR%=$CPAN::Config->{build_dir} ". "for '$for'\n" ); $ENV{PERL5LIB} = join $Config::Config{path_sep}, @dirs, @env; } else { my $cnt = keys %{$self->{is_tested}}; $CPAN::Frontend->optprint('perl5lib', "Prepending blib/arch and blib/lib of ". "$cnt build dirs to PERL5LIB; ". "for '$for'\n" ); $ENV{PERL5LIB} = join $Config::Config{path_sep}, @dirs, @env; } }} 1; __END__ package vmsish; our $VERSION = '1.04'; my $IsVMS = $^O eq 'VMS'; sub bits { my $bits = 0; my $sememe; foreach $sememe (@_) { # Those hints are defined in vms/vmsish.h : # HINT_M_VMSISH_STATUS and HINT_M_VMSISH_TIME $bits |= 0x40000000, next if $sememe eq 'status' || $sememe eq '$?'; $bits |= 0x80000000, next if $sememe eq 'time'; } $bits; } sub import { return unless $IsVMS; shift; $^H |= bits(@_ ? @_ : qw(status time)); my $sememe; foreach $sememe (@_ ? @_ : qw(exit hushed)) { $^H{'vmsish_exit'} = 1 if $sememe eq 'exit'; vmsish::hushed(1) if $sememe eq 'hushed'; } } sub unimport { return unless $IsVMS; shift; $^H &= ~ bits(@_ ? @_ : qw(status time)); my $sememe; foreach $sememe (@_ ? @_ : qw(exit hushed)) { $^H{'vmsish_exit'} = 0 if $sememe eq 'exit'; vmsish::hushed(0) if $sememe eq 'hushed'; } } 1; use 5.008; package base; use strict 'vars'; our $VERSION = '2.27'; $VERSION =~ tr/_//d; # simplest way to avoid indexing of the package: no package statement sub base::__inc::unhook { @INC = grep !(ref eq 'CODE' && $_ == $_[0]), @INC } # instance is blessed array of coderefs to be removed from @INC at scope exit sub base::__inc::scope_guard::DESTROY { base::__inc::unhook $_ for @{$_[0]} } # constant.pm is slow sub SUCCESS () { 1 } sub PUBLIC () { 2**0 } sub PRIVATE () { 2**1 } sub INHERITED () { 2**2 } sub PROTECTED () { 2**3 } my $Fattr = \%fields::attr; sub has_fields { my($base) = shift; my $fglob = ${"$base\::"}{FIELDS}; return( ($fglob && 'GLOB' eq ref($fglob) && *$fglob{HASH}) ? 1 : 0 ); } sub has_attr { my($proto) = shift; my($class) = ref $proto || $proto; return exists $Fattr->{$class}; } sub get_attr { $Fattr->{$_[0]} = [1] unless $Fattr->{$_[0]}; return $Fattr->{$_[0]}; } if ($] < 5.009) { *get_fields = sub { # Shut up a possible typo warning. () = \%{$_[0].'::FIELDS'}; my $f = \%{$_[0].'::FIELDS'}; # should be centralized in fields? perhaps # fields::mk_FIELDS_be_OK. Peh. As long as %{ $package . '::FIELDS' } # is used here anyway, it doesn't matter. bless $f, 'pseudohash' if (ref($f) ne 'pseudohash'); return $f; } } else { *get_fields = sub { # Shut up a possible typo warning. () = \%{$_[0].'::FIELDS'}; return \%{$_[0].'::FIELDS'}; } } if ($] < 5.008) { *_module_to_filename = sub { (my $fn = $_[0]) =~ s!::!/!g; $fn .= '.pm'; return $fn; } } else { *_module_to_filename = sub { (my $fn = $_[0]) =~ s!::!/!g; $fn .= '.pm'; utf8::encode($fn); return $fn; } } sub import { my $class = shift; return SUCCESS unless @_; # List of base classes from which we will inherit %FIELDS. my $fields_base; my $inheritor = caller(0); my @bases; foreach my $base (@_) { if ( $inheritor eq $base ) { warn "Class '$inheritor' tried to inherit from itself\n"; } next if grep $_->isa($base), ($inheritor, @bases); # Following blocks help isolate $SIG{__DIE__} and @INC changes { my $sigdie; { local $SIG{__DIE__}; my $fn = _module_to_filename($base); my $dot_hidden; eval { my $guard; if ($INC[-1] eq '.' && %{"$base\::"}) { # So: the package already exists => this an optional load # And: there is a dot at the end of @INC => we want to hide it # However: we only want to hide it during our *own* require() # (i.e. without affecting nested require()s). # So we add a hook to @INC whose job is to hide the dot, but which # first checks checks the callstack depth, because within nested # require()s the callstack is deeper. # Since CORE::GLOBAL::require makes it unknowable in advance what # the exact relevant callstack depth will be, we have to record it # inside a hook. So we put another hook just for that at the front # of @INC, where it's guaranteed to run -- immediately. # The dot-hiding hook does its job by sitting directly in front of # the dot and removing itself from @INC when reached. This causes # the dot to move up one index in @INC, causing the loop inside # pp_require() to skip it. # Loaded coded may disturb this precise arrangement, but that's OK # because the hook is inert by that time. It is only active during # the top-level require(), when @INC is in our control. The only # possible gotcha is if other hooks already in @INC modify @INC in # some way during that initial require(). # Note that this jiggery hookery works just fine recursively: if # a module loaded via base.pm uses base.pm itself, there will be # one pair of hooks in @INC per base::import call frame, but the # pairs from different nestings do not interfere with each other. my $lvl; unshift @INC, sub { return if defined $lvl; 1 while defined caller ++$lvl; () }; splice @INC, -1, 0, sub { return if defined caller $lvl; ++$dot_hidden, &base::__inc::unhook; () }; $guard = bless [ @INC[0,-2] ], 'base::__inc::scope_guard'; } require $fn }; if ($dot_hidden && (my @fn = grep -e && !( -d _ || -b _ ), $fn.'c', $fn)) { require Carp; Carp::croak(<]*> (?:line|chunk) [0-9]+)?\.\n\z/s || $@ =~ /Compilation failed in require at .* line [0-9]+(?:, <[^>]*> (?:line|chunk) [0-9]+)?\.\n\z/; unless (%{"$base\::"}) { require Carp; local $" = " "; Carp::croak(<[0] = @$battr; if( keys %$dfields ) { warn <<"END"; $derived is inheriting from $base but already has its own fields! This will cause problems. Be sure you use base BEFORE declaring fields. END } # Iterate through the base's fields adding all the non-private # ones to the derived class. Hang on to the original attribute # (Public, Private, etc...) and add Inherited. # This is all too complicated to do efficiently with add_fields(). while (my($k,$v) = each %$bfields) { my $fno; if ($fno = $dfields->{$k} and $fno != $v) { require Carp; Carp::croak ("Inherited fields can't override existing fields"); } if( $battr->[$v] & PRIVATE ) { $dattr->[$v] = PRIVATE | INHERITED; } else { $dattr->[$v] = INHERITED | $battr->[$v]; $dfields->{$k} = $v; } } foreach my $idx (1..$#{$battr}) { next if defined $dattr->[$idx]; $dattr->[$idx] = $battr->[$idx] & INHERITED; } } 1; __END__ # You may distribute under the terms of either the GNU General Public License # or the Artistic License (the same terms as Perl itself) # # (C) Paul Evans, 2011-2018 -- leonerd@leonerd.org.uk package Future; use strict; use warnings; no warnings 'recursion'; # Disable the "deep recursion" warning our $VERSION = '0.39'; use Carp qw(); # don't import croak use Scalar::Util qw( weaken blessed reftype ); use B qw( svref_2object ); use Time::HiRes qw( gettimeofday tv_interval ); # we are not overloaded, but we want to check if other objects are require overload; our @CARP_NOT = qw( Future::Utils ); use constant DEBUG => !!$ENV{PERL_FUTURE_DEBUG}; our $TIMES = DEBUG || $ENV{PERL_FUTURE_TIMES}; # Callback flags use constant { CB_DONE => 1<<0, # Execute callback on done CB_FAIL => 1<<1, # Execute callback on fail CB_CANCEL => 1<<2, # Execute callback on cancellation CB_SELF => 1<<3, # Pass $self as first argument CB_RESULT => 1<<4, # Pass result/failure as a list CB_SEQ_ONDONE => 1<<5, # Sequencing on success (->then) CB_SEQ_ONFAIL => 1<<6, # Sequencing on failure (->else) CB_SEQ_IMDONE => 1<<7, # $code is in fact immediate ->done result CB_SEQ_IMFAIL => 1<<8, # $code is in fact immediate ->fail result }; use constant CB_ALWAYS => CB_DONE|CB_FAIL|CB_CANCEL; # Useful for identifying CODE references sub CvNAME_FILE_LINE { my ( $code ) = @_; my $cv = svref_2object( $code ); my $name = join "::", $cv->STASH->NAME, $cv->GV->NAME; return $name unless $cv->GV->NAME eq "__ANON__"; # $cv->GV->LINE isn't reliable, as outside of perl -d mode all anon CODE # in the same file actually shares the same GV. :( # Walk the optree looking for the first COP my $cop = $cv->START; $cop = $cop->next while $cop and ref $cop ne "B::COP" and ref $cop ne "B::NULL"; return $cv->GV->NAME if ref $cop eq "B::NULL"; sprintf "%s(%s line %d)", $cv->GV->NAME, $cop->file, $cop->line; } sub _callable { my ( $cb ) = @_; defined $cb and ( reftype($cb) eq 'CODE' || overload::Method($cb, '&{}') ); } sub new { my $proto = shift; return bless { ready => 0, callbacks => [], # [] = [$type, ...] ( DEBUG ? ( do { my $at = Carp::shortmess( "constructed" ); chomp $at; $at =~ s/\.$//; constructed_at => $at } ) : () ), ( $TIMES ? ( btime => [ gettimeofday ] ) : () ), }, ( ref $proto || $proto ); } my $GLOBAL_END; END { $GLOBAL_END = 1; } sub DESTROY_debug { my $self = shift; return if $GLOBAL_END; return if $self->{ready} and ( $self->{reported} or !$self->{failure} ); my $lost_at = join " line ", (caller)[1,2]; # We can't actually know the real line where the last reference was lost; # a variable set to 'undef' or close of scope, because caller can't see it; # the current op has already been updated. The best we can do is indicate # 'near'. if( $self->{ready} and $self->{failure} ) { warn "${\$self->__selfstr} was $self->{constructed_at} and was lost near $lost_at with an unreported failure of: " . $self->{failure}[0] . "\n"; } elsif( !$self->{ready} ) { warn "${\$self->__selfstr} was $self->{constructed_at} and was lost near $lost_at before it was ready.\n"; } } *DESTROY = \&DESTROY_debug if DEBUG; sub wrap { my $class = shift; my @values = @_; if( @values == 1 and blessed $values[0] and $values[0]->isa( __PACKAGE__ ) ) { return $values[0]; } else { return $class->done( @values ); } } sub call { my $class = shift; my ( $code, @args ) = @_; my $f; eval { $f = $code->( @args ); 1 } or $f = $class->fail( $@ ); blessed $f and $f->isa( "Future" ) or $f = $class->fail( "Expected " . CvNAME_FILE_LINE($code) . " to return a Future" ); return $f; } sub _shortmess { my $at = Carp::shortmess( $_[0] ); chomp $at; $at =~ s/\.$//; return $at; } sub _mark_ready { my $self = shift; $self->{ready} = 1; $self->{ready_at} = _shortmess $_[0] if DEBUG; if( $TIMES ) { $self->{rtime} = [ gettimeofday ]; } delete $self->{on_cancel}; my $callbacks = delete $self->{callbacks} or return; my $cancelled = $self->{cancelled}; my $fail = defined $self->{failure}; my $done = !$fail && !$cancelled; my @result = $done ? $self->get : $fail ? $self->failure : (); foreach my $cb ( @$callbacks ) { my ( $flags, $code ) = @$cb; my $is_future = blessed( $code ) && $code->isa( "Future" ); next if $done and not( $flags & CB_DONE ); next if $fail and not( $flags & CB_FAIL ); next if $cancelled and not( $flags & CB_CANCEL ); $self->{reported} = 1 if $fail; if( $is_future ) { $done ? $code->done( @result ) : $fail ? $code->fail( @result ) : $code->cancel; } elsif( $flags & (CB_SEQ_ONDONE|CB_SEQ_ONFAIL) ) { my ( undef, undef, $fseq ) = @$cb; if( !$fseq ) { # weaken()ed; it might be gone now # This warning should always be printed, even not in DEBUG mode. # It's always an indication of a bug Carp::carp +(DEBUG ? "${\$self->__selfstr} ($self->{constructed_at})" : "${\$self->__selfstr} $self" ) . " lost a sequence Future"; next; } my $f2; if( $done and $flags & CB_SEQ_ONDONE or $fail and $flags & CB_SEQ_ONFAIL ) { if( $flags & CB_SEQ_IMDONE ) { $fseq->done( @$code ); next; } elsif( $flags & CB_SEQ_IMFAIL ) { $fseq->fail( @$code ); next; } my @args = ( ( $flags & CB_SELF ? $self : () ), ( $flags & CB_RESULT ? @result : () ), ); unless( eval { $f2 = $code->( @args ); 1 } ) { $fseq->fail( $@ ); next; } unless( blessed $f2 and $f2->isa( "Future" ) ) { $fseq->fail( "Expected " . CvNAME_FILE_LINE($code) . " to return a Future" ); next; } $fseq->on_cancel( $f2 ); } else { $f2 = $self; } if( $f2->is_ready ) { $f2->on_ready( $fseq ) if !$f2->{cancelled}; } else { push @{ $f2->{callbacks} }, [ CB_DONE|CB_FAIL, $fseq ]; weaken( $f2->{callbacks}[-1][1] ); } } else { $code->( ( $flags & CB_SELF ? $self : () ), ( $flags & CB_RESULT ? @result : () ), ); } } } sub is_ready { my $self = shift; return $self->{ready}; } sub is_done { my $self = shift; return $self->{ready} && !$self->{failure} && !$self->{cancelled}; } sub is_failed { my $self = shift; return $self->{ready} && !!$self->{failure}; # boolify } sub is_cancelled { my $self = shift; return $self->{cancelled}; } sub state { my $self = shift; return !$self->{ready} ? "pending" : DEBUG ? $self->{ready_at} : $self->{failure} ? "failed" : $self->{cancelled} ? "cancelled" : "done"; } sub done { my $self = shift; if( ref $self ) { $self->{cancelled} and return $self; $self->{ready} and Carp::croak "${\$self->__selfstr} is already ".$self->state." and cannot be ->done"; $self->{subs} and Carp::croak "${\$self->__selfstr} is not a leaf Future, cannot be ->done"; $self->{result} = [ @_ ]; $self->_mark_ready( "done" ); } else { $self = $self->new; $self->{ready} = 1; $self->{ready_at} = _shortmess "done" if DEBUG; $self->{result} = [ @_ ]; } return $self; } my $warned_done_cb; sub done_cb { my $self = shift; $warned_done_cb or $warned_done_cb++, warnings::warnif( deprecated => "Future->done_cb is now deprecated; use ->curry::done or sub {...}" ); return sub { $self->done( @_ ) }; } sub fail { my $self = shift; my ( $exception, @details ) = @_; $_[0] or Carp::croak "$self ->fail requires an exception that is true"; if( ref $self ) { $self->{cancelled} and return $self; $self->{ready} and Carp::croak "${\$self->__selfstr} is already ".$self->state." and cannot be ->fail'ed"; $self->{subs} and Carp::croak "${\$self->__selfstr} is not a leaf Future, cannot be ->fail'ed"; $self->{failure} = [ $exception, @details ]; $self->_mark_ready( "failed" ); } else { $self = $self->new; $self->{ready} = 1; $self->{ready_at} = _shortmess "failed" if DEBUG; $self->{failure} = [ $exception, @details ]; } return $self; } my $warned_fail_cb; sub fail_cb { my $self = shift; $warned_fail_cb or $warned_fail_cb++, warnings::warnif( deprecated => "Future->fail_cb is now deprecated; use ->curry::fail or sub {...}" ); return sub { $self->fail( @_ ) }; } sub die :method { my $self = shift; my ( $exception, @details ) = @_; if( !ref $exception and $exception !~ m/\n$/ ) { $exception .= sprintf " at %s line %d\n", (caller)[1,2]; } $self->fail( $exception, @details ); } sub on_cancel { my $self = shift; my ( $code ) = @_; my $is_future = blessed( $code ) && $code->isa( "Future" ); $is_future or _callable( $code ) or Carp::croak "Expected \$code to be callable or a Future in ->on_cancel"; $self->{ready} and return $self; push @{ $self->{on_cancel} }, $code; return $self; } sub on_ready { my $self = shift; my ( $code ) = @_; my $is_future = blessed( $code ) && $code->isa( "Future" ); $is_future or _callable( $code ) or Carp::croak "Expected \$code to be callable or a Future in ->on_ready"; if( $self->{ready} ) { my $fail = defined $self->{failure}; my $done = !$fail && !$self->{cancelled}; $self->{reported} = 1 if $fail; $is_future ? ( $done ? $code->done( $self->get ) : $fail ? $code->fail( $self->failure ) : $code->cancel ) : $code->( $self ); } else { push @{ $self->{callbacks} }, [ CB_ALWAYS|CB_SELF, $self->wrap_cb( on_ready => $code ) ]; } return $self; } sub await { my $self = shift; Carp::croak "$self is not yet complete and does not provide ->await"; } sub get { my $self = shift; until( $self->{ready} ) { $self->await } if( $self->{failure} ) { $self->{reported} = 1; my $exception = $self->{failure}->[0]; !ref $exception && $exception =~ m/\n$/ ? CORE::die $exception : Carp::croak $exception; } $self->{cancelled} and Carp::croak "${\$self->__selfstr} was cancelled"; return $self->{result}->[0] unless wantarray; return @{ $self->{result} }; } sub unwrap { shift; # $class my @values = @_; if( @values == 1 and blessed $values[0] and $values[0]->isa( __PACKAGE__ ) ) { return $values[0]->get; } else { return $values[0] if !wantarray; return @values; } } sub on_done { my $self = shift; my ( $code ) = @_; my $is_future = blessed( $code ) && $code->isa( "Future" ); $is_future or _callable( $code ) or Carp::croak "Expected \$code to be callable or a Future in ->on_done"; if( $self->{ready} ) { return $self if $self->{failure} or $self->{cancelled}; $is_future ? $code->done( $self->get ) : $code->( $self->get ); } else { push @{ $self->{callbacks} }, [ CB_DONE|CB_RESULT, $self->wrap_cb( on_done => $code ) ]; } return $self; } sub failure { my $self = shift; until( $self->{ready} ) { $self->await } return unless $self->{failure}; $self->{reported} = 1; return $self->{failure}->[0] if !wantarray; return @{ $self->{failure} }; } sub on_fail { my $self = shift; my ( $code ) = @_; my $is_future = blessed( $code ) && $code->isa( "Future" ); $is_future or _callable( $code ) or Carp::croak "Expected \$code to be callable or a Future in ->on_fail"; if( $self->{ready} ) { return $self if not $self->{failure}; $self->{reported} = 1; $is_future ? $code->fail( $self->failure ) : $code->( $self->failure ); } else { push @{ $self->{callbacks} }, [ CB_FAIL|CB_RESULT, $self->wrap_cb( on_fail => $code ) ]; } return $self; } sub cancel { my $self = shift; return $self if $self->{ready}; $self->{cancelled}++; foreach my $code ( reverse @{ $self->{on_cancel} || [] } ) { my $is_future = blessed( $code ) && $code->isa( "Future" ); $is_future ? $code->cancel : $code->( $self ); } $self->_mark_ready( "cancel" ); return $self; } my $warned_cancel_cb; sub cancel_cb { my $self = shift; $warned_cancel_cb or $warned_cancel_cb++, warnings::warnif( deprecated => "Future->cancel_cb is now deprecated; use ->curry::cancel or sub {...}" ); return sub { $self->cancel }; } sub _sequence { my $f1 = shift; my ( $code, $flags ) = @_; # For later, we might want to know where we were called from my $func = (caller 1)[3]; $func =~ s/^.*:://; $flags & (CB_SEQ_IMDONE|CB_SEQ_IMFAIL) or _callable( $code ) or Carp::croak "Expected \$code to be callable in ->$func"; if( !defined wantarray ) { Carp::carp "Calling ->$func in void context"; } if( $f1->is_ready ) { # Take a shortcut return $f1 if $f1->is_done and not( $flags & CB_SEQ_ONDONE ) or $f1->failure and not( $flags & CB_SEQ_ONFAIL ); if( $flags & CB_SEQ_IMDONE ) { return Future->done( @$code ); } elsif( $flags & CB_SEQ_IMFAIL ) { return Future->fail( @$code ); } my @args = ( ( $flags & CB_SELF ? $f1 : () ), ( $flags & CB_RESULT ? $f1->is_done ? $f1->get : $f1->failure ? $f1->failure : () : () ), ); my $fseq; unless( eval { $fseq = $code->( @args ); 1 } ) { return Future->fail( $@ ); } unless( blessed $fseq and $fseq->isa( "Future" ) ) { return Future->fail( "Expected " . CvNAME_FILE_LINE($code) . " to return a Future" ); } return $fseq; } my $fseq = $f1->new; $fseq->on_cancel( $f1 ); # TODO: if anyone cares about the op name, we might have to synthesize it # from $flags $code = $f1->wrap_cb( sequence => $code ) unless $flags & (CB_SEQ_IMDONE|CB_SEQ_IMFAIL); push @{ $f1->{callbacks} }, [ CB_DONE|CB_FAIL|$flags, $code, $fseq ]; weaken( $f1->{callbacks}[-1][2] ); return $fseq; } my $make_donecatchfail_sub = sub { my ( $with_f, $done_code, $fail_code, @catch_list ) = @_; my $func = (caller 1)[3]; $func =~ s/^.*:://; !$done_code or _callable( $done_code ) or Carp::croak "Expected \$done_code to be callable in ->$func"; !$fail_code or _callable( $fail_code ) or Carp::croak "Expected \$fail_code to be callable in ->$func"; my %catch_handlers = @catch_list; _callable( $catch_handlers{$_} ) or Carp::croak "Expected catch handler for '$_' to be callable in ->$func" for keys %catch_handlers; sub { my $self = shift; my @maybe_self = $with_f ? ( $self ) : (); if( !$self->{failure} ) { return $self unless $done_code; return $done_code->( @maybe_self, $self->get ); } else { my $name = $self->{failure}[1]; if( defined $name and $catch_handlers{$name} ) { return $catch_handlers{$name}->( @maybe_self, $self->failure ); } return $self unless $fail_code; return $fail_code->( @maybe_self, $self->failure ); } }; }; sub then { my $self = shift; my $done_code = shift; my $fail_code = ( @_ % 2 ) ? pop : undef; my @catch_list = @_; if( $done_code and !@catch_list and !$fail_code ) { return $self->_sequence( $done_code, CB_SEQ_ONDONE|CB_RESULT ); } # Complex return $self->_sequence( $make_donecatchfail_sub->( 0, $done_code, $fail_code, @catch_list, ), CB_SEQ_ONDONE|CB_SEQ_ONFAIL|CB_SELF ); } sub else { my $self = shift; my ( $fail_code ) = @_; return $self->_sequence( $fail_code, CB_SEQ_ONFAIL|CB_RESULT ); } sub catch { my $self = shift; my $fail_code = ( @_ % 2 ) ? pop : undef; my @catch_list = @_; return $self->_sequence( $make_donecatchfail_sub->( 0, undef, $fail_code, @catch_list, ), CB_SEQ_ONDONE|CB_SEQ_ONFAIL|CB_SELF ); } sub transform { my $self = shift; my %args = @_; my $xfrm_done = $args{done}; my $xfrm_fail = $args{fail}; return $self->_sequence( sub { my $self = shift; if( !$self->{failure} ) { return $self unless $xfrm_done; my @result = $xfrm_done->( $self->get ); return $self->new->done( @result ); } else { return $self unless $xfrm_fail; my @failure = $xfrm_fail->( $self->failure ); return $self->new->fail( @failure ); } }, CB_SEQ_ONDONE|CB_SEQ_ONFAIL|CB_SELF ); } sub then_with_f { my $self = shift; my $done_code = shift; my $fail_code = ( @_ % 2 ) ? pop : undef; my @catch_list = @_; if( $done_code and !@catch_list and !$fail_code ) { return $self->_sequence( $done_code, CB_SEQ_ONDONE|CB_SELF|CB_RESULT ); } return $self->_sequence( $make_donecatchfail_sub->( 1, $done_code, $fail_code, @catch_list, ), CB_SEQ_ONDONE|CB_SEQ_ONFAIL|CB_SELF ); } sub then_done { my $self = shift; my ( @result ) = @_; return $self->_sequence( \@result, CB_SEQ_ONDONE|CB_SEQ_IMDONE ); } sub then_fail { my $self = shift; my ( @failure ) = @_; return $self->_sequence( \@failure, CB_SEQ_ONDONE|CB_SEQ_IMFAIL ); } sub else_with_f { my $self = shift; my ( $fail_code ) = @_; return $self->_sequence( $fail_code, CB_SEQ_ONFAIL|CB_SELF|CB_RESULT ); } sub else_done { my $self = shift; my ( @result ) = @_; return $self->_sequence( \@result, CB_SEQ_ONFAIL|CB_SEQ_IMDONE ); } sub else_fail { my $self = shift; my ( @failure ) = @_; return $self->_sequence( \@failure, CB_SEQ_ONFAIL|CB_SEQ_IMFAIL ); } sub catch_with_f { my $self = shift; my $fail_code = ( @_ % 2 ) ? pop : undef; my @catch_list = @_; return $self->_sequence( $make_donecatchfail_sub->( 1, undef, $fail_code, @catch_list, ), CB_SEQ_ONDONE|CB_SEQ_ONFAIL|CB_SELF ); } sub followed_by { my $self = shift; my ( $code ) = @_; return $self->_sequence( $code, CB_SEQ_ONDONE|CB_SEQ_ONFAIL|CB_SELF ); } sub without_cancel { my $self = shift; my $new = $self->new; $self->on_ready( sub { my $self = shift; if( $self->failure ) { $new->fail( $self->failure ); } else { $new->done( $self->get ); } }); $new->{orig} = $self; # just to strongref it - RT122920 $new->on_ready( sub { undef $_[0]->{orig} } ); return $new; } sub retain { my $self = shift; return $self->on_ready( sub { undef $self } ); } sub _new_convergent { shift; # ignore this class my ( $subs ) = @_; foreach my $sub ( @$subs ) { blessed $sub and $sub->isa( "Future" ) or Carp::croak "Expected a Future, got $sub"; } # Find the best prototype. Ideally anything derived if we can find one. my $self; ref($_) eq "Future" or $self = $_->new, last for @$subs; # No derived ones; just have to be a basic class then $self ||= Future->new; $self->{subs} = $subs; # This might be called by a DESTROY during global destruction so it should # be as defensive as possible (see RT88967) $self->on_cancel( sub { foreach my $sub ( @$subs ) { $sub->cancel if $sub and !$sub->{ready}; } } ); return $self; } sub wait_all { my $class = shift; my @subs = @_; unless( @subs ) { my $self = $class->done; $self->{subs} = []; return $self; } my $self = Future->_new_convergent( \@subs ); my $pending = 0; $_->{ready} or $pending++ for @subs; # Look for immediate ready if( !$pending ) { $self->{result} = [ @subs ]; $self->_mark_ready( "wait_all" ); return $self; } weaken( my $weakself = $self ); my $sub_on_ready = sub { return unless my $self = $weakself; $pending--; $pending and return; $self->{result} = [ @subs ]; $self->_mark_ready( "wait_all" ); }; foreach my $sub ( @subs ) { $sub->{ready} or $sub->on_ready( $sub_on_ready ); } return $self; } sub wait_any { my $class = shift; my @subs = @_; unless( @subs ) { my $self = $class->fail( "Cannot ->wait_any with no subfutures" ); $self->{subs} = []; return $self; } my $self = Future->_new_convergent( \@subs ); # Look for immediate ready my $immediate_ready; foreach my $sub ( @subs ) { $sub->{ready} and $immediate_ready = $sub, last; } if( $immediate_ready ) { foreach my $sub ( @subs ) { $sub->{ready} or $sub->cancel; } if( $immediate_ready->{failure} ) { $self->{failure} = [ $immediate_ready->failure ]; } else { $self->{result} = [ $immediate_ready->get ]; } $self->_mark_ready( "wait_any" ); return $self; } my $pending = 0; weaken( my $weakself = $self ); my $sub_on_ready = sub { return unless my $self = $weakself; return if $self->{result} or $self->{failure}; # don't recurse on child ->cancel return if --$pending and $_[0]->{cancelled}; if( $_[0]->{cancelled} ) { $self->{failure} = [ "All component futures were cancelled" ]; } elsif( $_[0]->{failure} ) { $self->{failure} = [ $_[0]->failure ]; } else { $self->{result} = [ $_[0]->get ]; } foreach my $sub ( @subs ) { $sub->{ready} or $sub->cancel; } $self->_mark_ready( "wait_any" ); }; foreach my $sub ( @subs ) { # No need to test $sub->{ready} since we know none of them are $sub->on_ready( $sub_on_ready ); $pending++; } return $self; } sub needs_all { my $class = shift; my @subs = @_; unless( @subs ) { my $self = $class->done; $self->{subs} = []; return $self; } my $self = Future->_new_convergent( \@subs ); # Look for immediate fail my $immediate_fail; foreach my $sub ( @subs ) { $sub->{ready} and $sub->{failure} and $immediate_fail = $sub, last; } if( $immediate_fail ) { foreach my $sub ( @subs ) { $sub->{ready} or $sub->cancel; } $self->{failure} = [ $immediate_fail->failure ]; $self->_mark_ready( "needs_all" ); return $self; } my $pending = 0; $_->{ready} or $pending++ for @subs; # Look for immediate done if( !$pending ) { $self->{result} = [ map { $_->get } @subs ]; $self->_mark_ready( "needs_all" ); return $self; } weaken( my $weakself = $self ); my $sub_on_ready = sub { return unless my $self = $weakself; return if $self->{result} or $self->{failure}; # don't recurse on child ->cancel if( $_[0]->{cancelled} ) { $self->{failure} = [ "A component future was cancelled" ]; foreach my $sub ( @subs ) { $sub->cancel if !$sub->{ready}; } $self->_mark_ready( "needs_all" ); } elsif( my @failure = $_[0]->failure ) { $self->{failure} = \@failure; foreach my $sub ( @subs ) { $sub->cancel if !$sub->{ready}; } $self->_mark_ready( "needs_all" ); } else { $pending--; $pending and return; $self->{result} = [ map { $_->get } @subs ]; $self->_mark_ready( "needs_all" ); } }; foreach my $sub ( @subs ) { $sub->{ready} or $sub->on_ready( $sub_on_ready ); } return $self; } sub needs_any { my $class = shift; my @subs = @_; unless( @subs ) { my $self = $class->fail( "Cannot ->needs_any with no subfutures" ); $self->{subs} = []; return $self; } my $self = Future->_new_convergent( \@subs ); # Look for immediate done my $immediate_done; my $pending = 0; foreach my $sub ( @subs ) { $sub->{ready} and !$sub->{failure} and $immediate_done = $sub, last; $sub->{ready} or $pending++; } if( $immediate_done ) { foreach my $sub ( @subs ) { $sub->{ready} ? $sub->{reported} = 1 : $sub->cancel; } $self->{result} = [ $immediate_done->get ]; $self->_mark_ready( "needs_any" ); return $self; } # Look for immediate fail my $immediate_fail = 1; foreach my $sub ( @subs ) { $sub->{ready} or $immediate_fail = 0, last; } if( $immediate_fail ) { $_->{reported} = 1 for @subs; # For consistency we'll pick the last one for the failure $self->{failure} = [ $subs[-1]->{failure} ]; $self->_mark_ready( "needs_any" ); return $self; } weaken( my $weakself = $self ); my $sub_on_ready = sub { return unless my $self = $weakself; return if $self->{result} or $self->{failure}; # don't recurse on child ->cancel return if --$pending and $_[0]->{cancelled}; if( $_[0]->{cancelled} ) { $self->{failure} = [ "All component futures were cancelled" ]; $self->_mark_ready( "needs_any" ); } elsif( my @failure = $_[0]->failure ) { $pending and return; $self->{failure} = \@failure; $self->_mark_ready( "needs_any" ); } else { $self->{result} = [ $_[0]->get ]; foreach my $sub ( @subs ) { $sub->cancel if !$sub->{ready}; } $self->_mark_ready( "needs_any" ); } }; foreach my $sub ( @subs ) { $sub->{ready} or $sub->on_ready( $sub_on_ready ); } return $self; } sub pending_futures { my $self = shift; $self->{subs} or Carp::croak "Cannot call ->pending_futures on a non-convergent Future"; return grep { not $_->{ready} } @{ $self->{subs} }; } sub ready_futures { my $self = shift; $self->{subs} or Carp::croak "Cannot call ->ready_futures on a non-convergent Future"; return grep { $_->{ready} } @{ $self->{subs} }; } sub done_futures { my $self = shift; $self->{subs} or Carp::croak "Cannot call ->done_futures on a non-convergent Future"; return grep { $_->{ready} and not $_->{failure} and not $_->{cancelled} } @{ $self->{subs} }; } sub failed_futures { my $self = shift; $self->{subs} or Carp::croak "Cannot call ->failed_futures on a non-convergent Future"; return grep { $_->{ready} and $_->{failure} } @{ $self->{subs} }; } sub cancelled_futures { my $self = shift; $self->{subs} or Carp::croak "Cannot call ->cancelled_futures on a non-convergent Future"; return grep { $_->{ready} and $_->{cancelled} } @{ $self->{subs} }; } sub set_label { my $self = shift; ( $self->{label} ) = @_; return $self; } sub label { my $self = shift; return $self->{label}; } sub __selfstr { my $self = shift; return "$self" unless defined $self->{label}; return "$self (\"$self->{label}\")"; } sub btime { my $self = shift; return $self->{btime}; } sub rtime { my $self = shift; return $self->{rtime}; } sub elapsed { my $self = shift; return undef unless defined $self->{btime} and defined $self->{rtime}; return $self->{elapsed} ||= tv_interval( $self->{btime}, $self->{rtime} ); } sub wrap_cb { my $self = shift; my ( $op, $cb ) = @_; return $cb; } 0x55AA; package bytes; sub length (_) { BEGIN { bytes::import() } return CORE::length($_[0]); } sub substr ($$;$$) { BEGIN { bytes::import() } return @_ == 2 ? CORE::substr($_[0], $_[1]) : @_ == 3 ? CORE::substr($_[0], $_[1], $_[2]) : CORE::substr($_[0], $_[1], $_[2], $_[3]) ; } sub ord (_) { BEGIN { bytes::import() } return CORE::ord($_[0]); } sub chr (_) { BEGIN { bytes::import() } return CORE::chr($_[0]); } sub index ($$;$) { BEGIN { bytes::import() } return @_ == 2 ? CORE::index($_[0], $_[1]) : CORE::index($_[0], $_[1], $_[2]) ; } sub rindex ($$;$) { BEGIN { bytes::import() } return @_ == 2 ? CORE::rindex($_[0], $_[1]) : CORE::rindex($_[0], $_[1], $_[2]) ; } 1; package constant; use 5.008; use strict; use warnings::register; our $VERSION = '1.33'; our %declared; #======================================================================= # Some names are evil choices. my %keywords = map +($_, 1), qw{ BEGIN INIT CHECK END DESTROY AUTOLOAD }; $keywords{UNITCHECK}++ if $] > 5.009; my %forced_into_main = map +($_, 1), qw{ STDIN STDOUT STDERR ARGV ARGVOUT ENV INC SIG }; my %forbidden = (%keywords, %forced_into_main); my $normal_constant_name = qr/^_?[^\W_0-9]\w*\z/; my $tolerable = qr/^[A-Za-z_]\w*\z/; my $boolean = qr/^[01]?\z/; BEGIN { # We'd like to do use constant _CAN_PCS => $] > 5.009002 # but that's a bit tricky before we load the constant module :-) # By doing this, we save several run time checks for *every* call # to import. my $const = $] > 5.009002; my $downgrade = $] < 5.015004; # && $] >= 5.008 my $constarray = exists &_make_const; if ($const) { Internals::SvREADONLY($const, 1); Internals::SvREADONLY($downgrade, 1); $constant::{_CAN_PCS} = \$const; $constant::{_DOWNGRADE} = \$downgrade; $constant::{_CAN_PCS_FOR_ARRAY} = \$constarray; } else { no strict 'refs'; *{"_CAN_PCS"} = sub () {$const}; *{"_DOWNGRADE"} = sub () { $downgrade }; *{"_CAN_PCS_FOR_ARRAY"} = sub () { $constarray }; } } #======================================================================= # import() - import symbols into user's namespace # # What we actually do is define a function in the caller's namespace # which returns the value. The function we create will normally # be inlined as a constant, thereby avoiding further sub calling # overhead. #======================================================================= sub import { my $class = shift; return unless @_; # Ignore 'use constant;' my $constants; my $multiple = ref $_[0]; my $caller = caller; my $flush_mro; my $symtab; if (_CAN_PCS) { no strict 'refs'; $symtab = \%{$caller . '::'}; }; if ( $multiple ) { if (ref $_[0] ne 'HASH') { require Carp; Carp::croak("Invalid reference type '".ref(shift)."' not 'HASH'"); } $constants = shift; } else { unless (defined $_[0]) { require Carp; Carp::croak("Can't use undef as constant name"); } $constants->{+shift} = undef; } foreach my $name ( keys %$constants ) { my $pkg; my $symtab = $symtab; my $orig_name = $name; if ($name =~ s/(.*)(?:::|')(?=.)//s) { $pkg = $1; if (_CAN_PCS && $pkg ne $caller) { no strict 'refs'; $symtab = \%{$pkg . '::'}; } } else { $pkg = $caller; } # Normal constant name if ($name =~ $normal_constant_name and !$forbidden{$name}) { # Everything is okay # Name forced into main, but we're not in main. Fatal. } elsif ($forced_into_main{$name} and $pkg ne 'main') { require Carp; Carp::croak("Constant name '$name' is forced into main::"); # Starts with double underscore. Fatal. } elsif ($name =~ /^__/) { require Carp; Carp::croak("Constant name '$name' begins with '__'"); # Maybe the name is tolerable } elsif ($name =~ $tolerable) { # Then we'll warn only if you've asked for warnings if (warnings::enabled()) { if ($keywords{$name}) { warnings::warn("Constant name '$name' is a Perl keyword"); } elsif ($forced_into_main{$name}) { warnings::warn("Constant name '$name' is " . "forced into package main::"); } } # Looks like a boolean # use constant FRED == fred; } elsif ($name =~ $boolean) { require Carp; if (@_) { Carp::croak("Constant name '$name' is invalid"); } else { Carp::croak("Constant name looks like boolean value"); } } else { # Must have bad characters require Carp; Carp::croak("Constant name '$name' has invalid characters"); } { no strict 'refs'; my $full_name = "${pkg}::$name"; $declared{$full_name}++; if ($multiple || @_ == 1) { my $scalar = $multiple ? $constants->{$orig_name} : $_[0]; if (_DOWNGRADE) { # for 5.8 to 5.14 # Work around perl bug #31991: Sub names (actually glob # names in general) ignore the UTF8 flag. So we have to # turn it off to get the "right" symbol table entry. utf8::is_utf8 $name and utf8::encode $name; } # The constant serves to optimise this entire block out on # 5.8 and earlier. if (_CAN_PCS) { # Use a reference as a proxy for a constant subroutine. # If this is not a glob yet, it saves space. If it is # a glob, we must still create it this way to get the # right internal flags set, as constants are distinct # from subroutines created with sub(){...}. # The check in Perl_ck_rvconst knows that inlinable # constants from cv_const_sv are read only. So we have to: Internals::SvREADONLY($scalar, 1); if (!exists $symtab->{$name}) { $symtab->{$name} = \$scalar; ++$flush_mro->{$pkg}; } else { local $constant::{_dummy} = \$scalar; *$full_name = \&{"_dummy"}; } } else { *$full_name = sub () { $scalar }; } } elsif (@_) { my @list = @_; if (_CAN_PCS_FOR_ARRAY) { _make_const($list[$_]) for 0..$#list; _make_const(@list); if (!exists $symtab->{$name}) { $symtab->{$name} = \@list; $flush_mro->{$pkg}++; } else { local $constant::{_dummy} = \@list; *$full_name = \&{"_dummy"}; } } else { *$full_name = sub () { @list }; } } else { *$full_name = sub () { }; } } } # Flush the cache exactly once if we make any direct symbol table changes. if (_CAN_PCS && $flush_mro) { mro::method_changed_in($_) for keys %$flush_mro; } } 1; __END__ package AutoLoader; use strict; use 5.006_001; our($VERSION, $AUTOLOAD); my $is_dosish; my $is_epoc; my $is_vms; my $is_macos; BEGIN { $is_dosish = $^O eq 'dos' || $^O eq 'os2' || $^O eq 'MSWin32' || $^O eq 'NetWare'; $is_epoc = $^O eq 'epoc'; $is_vms = $^O eq 'VMS'; $is_macos = $^O eq 'MacOS'; $VERSION = '5.74'; } AUTOLOAD { my $sub = $AUTOLOAD; autoload_sub($sub); goto &$sub; } sub autoload_sub { my $sub = shift; my $filename = AutoLoader::find_filename( $sub ); my $save = $@; local $!; # Do not munge the value. eval { local $SIG{__DIE__}; require $filename }; if ($@) { if (substr($sub,-9) eq '::DESTROY') { no strict 'refs'; *$sub = sub {}; $@ = undef; } elsif ($@ =~ /^Can't locate/) { # The load might just have failed because the filename was too # long for some old SVR3 systems which treat long names as errors. # If we can successfully truncate a long name then it's worth a go. # There is a slight risk that we could pick up the wrong file here # but autosplit should have warned about that when splitting. if ($filename =~ s/(\w{12,})\.al$/substr($1,0,11).".al"/e){ eval { local $SIG{__DIE__}; require $filename }; } } if ($@){ $@ =~ s/ at .*\n//; my $error = $@; require Carp; Carp::croak($error); } } $@ = $save; return 1; } sub find_filename { my $sub = shift; my $filename; # Braces used to preserve $1 et al. { # Try to find the autoloaded file from the package-qualified # name of the sub. e.g., if the sub needed is # Getopt::Long::GetOptions(), then $INC{Getopt/Long.pm} is # something like '/usr/lib/perl5/Getopt/Long.pm', and the # autoload file is '/usr/lib/perl5/auto/Getopt/Long/GetOptions.al'. # # However, if @INC is a relative path, this might not work. If, # for example, @INC = ('lib'), then $INC{Getopt/Long.pm} is # 'lib/Getopt/Long.pm', and we want to require # 'auto/Getopt/Long/GetOptions.al' (without the leading 'lib'). # In this case, we simple prepend the 'auto/' and let the # C take care of the searching for us. my ($pkg,$func) = ($sub =~ /(.*)::([^:]+)$/); $pkg =~ s#::#/#g; if (defined($filename = $INC{"$pkg.pm"})) { if ($is_macos) { $pkg =~ tr#/#:#; $filename = undef unless $filename =~ s#^(.*)$pkg\.pm\z#$1auto:$pkg:$func.al#s; } else { $filename = undef unless $filename =~ s#^(.*)$pkg\.pm\z#$1auto/$pkg/$func.al#s; } # if the file exists, then make sure that it is a # a fully anchored path (i.e either '/usr/lib/auto/foo/bar.al', # or './lib/auto/foo/bar.al'. This avoids C searching # (and failing) to find the 'lib/auto/foo/bar.al' because it # looked for 'lib/lib/auto/foo/bar.al', given @INC = ('lib'). if (defined $filename and -r $filename) { unless ($filename =~ m|^/|s) { if ($is_dosish) { unless ($filename =~ m{^([a-z]:)?[\\/]}is) { if ($^O ne 'NetWare') { $filename = "./$filename"; } else { $filename = "$filename"; } } } elsif ($is_epoc) { unless ($filename =~ m{^([a-z?]:)?[\\/]}is) { $filename = "./$filename"; } } elsif ($is_vms) { # XXX todo by VMSmiths $filename = "./$filename"; } elsif (!$is_macos) { $filename = "./$filename"; } } } else { $filename = undef; } } unless (defined $filename) { # let C do the searching $filename = "auto/$sub.al"; $filename =~ s#::#/#g; } } return $filename; } sub import { my $pkg = shift; my $callpkg = caller; # # Export symbols, but not by accident of inheritance. # if ($pkg eq 'AutoLoader') { if ( @_ and $_[0] =~ /^&?AUTOLOAD$/ ) { no strict 'refs'; *{ $callpkg . '::AUTOLOAD' } = \&AUTOLOAD; } } # # Try to find the autosplit index file. Eg., if the call package # is POSIX, then $INC{POSIX.pm} is something like # '/usr/local/lib/perl5/POSIX.pm', and the autosplit index file is in # '/usr/local/lib/perl5/auto/POSIX/autosplit.ix', so we require that. # # However, if @INC is a relative path, this might not work. If, # for example, @INC = ('lib'), then # $INC{POSIX.pm} is 'lib/POSIX.pm', and we want to require # 'auto/POSIX/autosplit.ix' (without the leading 'lib'). # (my $calldir = $callpkg) =~ s#::#/#g; my $path = $INC{$calldir . '.pm'}; if (defined($path)) { # Try absolute path name, but only eval it if the # transformation from module path to autosplit.ix path # succeeded! my $replaced_okay; if ($is_macos) { (my $malldir = $calldir) =~ tr#/#:#; $replaced_okay = ($path =~ s#^(.*)$malldir\.pm\z#$1auto:$malldir:autosplit.ix#s); } else { $replaced_okay = ($path =~ s#^(.*)$calldir\.pm\z#$1auto/$calldir/autosplit.ix#); } eval { require $path; } if $replaced_okay; # If that failed, try relative path with normal @INC searching. if (!$replaced_okay or $@) { $path ="auto/$calldir/autosplit.ix"; eval { require $path; }; } if ($@) { my $error = $@; require Carp; Carp::carp($error); } } } sub unimport { my $callpkg = caller; no strict 'refs'; for my $exported (qw( AUTOLOAD )) { my $symname = $callpkg . '::' . $exported; undef *{ $symname } if \&{ $symname } == \&{ $exported }; *{ $symname } = \&{ $symname }; } } 1; __END__ require 5.014; # For more reliable $@ after eval package dumpvar; # Needed for PrettyPrinter only: # require 5.001; # Well, it coredumps anyway undef DB in 5.000 (not now) # translate control chars to ^X - Randal Schwartz # Modifications to print types by Peter Gordon v1.0 # Ilya Zakharevich -- patches after 5.001 (and some before ;-) # Won't dump symbol tables and contents of debugged files by default $winsize = 80 unless defined $winsize; sub ASCII { return ord('A') == 65; } # Defaults # $globPrint = 1; $printUndef = 1 unless defined $printUndef; $tick = "auto" unless defined $tick; $unctrl = 'quote' unless defined $unctrl; $subdump = 1; $dumpReused = 0 unless defined $dumpReused; $bareStringify = 1 unless defined $bareStringify; my $APC = chr utf8::unicode_to_native(0x9F); my $backslash_c_question = (ASCII) ? '\177' : $APC; sub main::dumpValue { local %address; local $^W=0; (print "undef\n"), return unless defined $_[0]; (print &stringify($_[0]), "\n"), return unless ref $_[0]; push @_, -1 if @_ == 1; dumpvar::unwrap($_[0], 0, $_[1]); } # This one is good for variable names: sub unctrl { for (my($dummy) = shift) { local($v) ; return \$_ if ref \$_ eq "GLOB"; s/([\000-\037])/ '^' . chr(utf8::unicode_to_native(ord($1)^64))/eg; s/ $backslash_c_question /^?/xg; return $_; } } sub uniescape { join("", map { $_ > 255 ? sprintf("\\x{%04X}", $_) : chr($_) } unpack("W*", $_[0])); } sub stringify { my $string; if (eval { $string = _stringify(@_); 1 }) { return $string; } return "<< value could not be dumped: $@ >>"; } sub _stringify { (my $__, local $noticks) = @_; for ($__) { local($v) ; my $tick = $tick; return 'undef' unless defined $_ or not $printUndef; return $_ . "" if ref \$_ eq 'GLOB'; $_ = &{'overload::StrVal'}($_) if $bareStringify and ref $_ and %overload:: and defined &{'overload::StrVal'}; if ($tick eq 'auto') { if (/[^[:^cntrl:]\n]/u) { # All controls but \n get '"' $tick = '"'; } else { $tick = "'"; } } if ($tick eq "'") { s/([\'\\])/\\$1/g; } elsif ($unctrl eq 'unctrl') { s/([\"\\])/\\$1/g ; $_ = &unctrl($_); # uniescape? s/([[:^ascii:]])/'\\0x'.sprintf('%2X',ord($1))/eg if $quoteHighBit; } elsif ($unctrl eq 'quote') { s/([\"\\\$\@])/\\$1/g if $tick eq '"'; s/\e/\\e/g; s/([\000-\037$backslash_c_question])/'\\c'._escaped_ord($1)/eg; } $_ = uniescape($_); s/([[:^ascii:]])/'\\'.sprintf('%3o',ord($1))/eg if $quoteHighBit; return ($noticks || /^\d+(\.\d*)?\Z/) ? $_ : $tick . $_ . $tick; } } # Ensure a resulting \ is escaped to be \\ sub _escaped_ord { my $chr = shift; if ($chr eq $backslash_c_question) { $chr = '?'; } else { $chr = chr(utf8::unicode_to_native(ord($chr)^64)); $chr =~ s{\\}{\\\\}g; } return $chr; } sub ShortArray { my $tArrayDepth = $#{$_[0]} ; $tArrayDepth = $#{$_[0]} < $arrayDepth-1 ? $#{$_[0]} : $arrayDepth-1 unless $arrayDepth eq '' ; my $shortmore = ""; $shortmore = " ..." if $tArrayDepth < $#{$_[0]} ; if (!grep(ref $_, @{$_[0]})) { $short = "0..$#{$_[0]} '" . join("' '", @{$_[0]}[0..$tArrayDepth]) . "'$shortmore"; return $short if length $short <= $compactDump; } undef; } sub DumpElem { my $short = &stringify($_[0], ref $_[0]); if ($veryCompact && ref $_[0] && (ref $_[0] eq 'ARRAY' and !grep(ref $_, @{$_[0]}) )) { my $end = "0..$#{$v} '" . join("' '", @{$_[0]}[0..$tArrayDepth]) . "'$shortmore"; } elsif ($veryCompact && ref $_[0] && (ref $_[0] eq 'HASH') and !grep(ref $_, values %{$_[0]})) { my $end = 1; $short = $sp . "0..$#{$v} '" . join("' '", @{$v}[0..$tArrayDepth]) . "'$shortmore"; } else { print "$short\n"; unwrap($_[0],$_[1],$_[2]) if ref $_[0]; } } sub unwrap { return if $DB::signal; local($v) = shift ; local($s) = shift ; # extra no of spaces local($m) = shift ; # maximum recursion depth return if $m == 0; local(%v,@v,$sp,$value,$key,@sortKeys,$more,$shortmore,$short) ; local($tHashDepth,$tArrayDepth) ; $sp = " " x $s ; $s += 3 ; eval { # Check for reused addresses if (ref $v) { my $val = $v; $val = &{'overload::StrVal'}($v) if %overload:: and defined &{'overload::StrVal'}; # Match type and address. # Unblessed references will look like TYPE(0x...) # Blessed references will look like Class=TYPE(0x...) $val =~ s/^.*=//; # suppress the Class part, just keep TYPE(0x...) ($item_type, $address) = $val =~ /([^\(]+) # Keep stuff that's # not an open paren \( # Skip open paren (0x[0-9a-f]+) # Save the address \) # Skip close paren $/x; # Should be at end now if (!$dumpReused && defined $address) { $address{$address}++ ; if ( $address{$address} > 1 ) { print "${sp}-> REUSED_ADDRESS\n" ; return ; } } } elsif (ref \$v eq 'GLOB') { # This is a raw glob. Special handling for that. $address = "$v" . ""; # To avoid a bug with globs $address{$address}++ ; if ( $address{$address} > 1 ) { print "${sp}*DUMPED_GLOB*\n" ; return ; } } if (ref $v eq 'Regexp') { # Reformat the regexp to look the standard way. my $re = "$v"; $re =~ s,/,\\/,g; print "$sp-> qr/$re/\n"; return; } if ( $item_type eq 'HASH' ) { # Hash ref or hash-based object. my @sortKeys = sort keys(%$v) ; undef $more ; $tHashDepth = $#sortKeys ; $tHashDepth = $#sortKeys < $hashDepth-1 ? $#sortKeys : $hashDepth-1 unless $hashDepth eq '' ; $more = "....\n" if $tHashDepth < $#sortKeys ; $shortmore = ""; $shortmore = ", ..." if $tHashDepth < $#sortKeys ; $#sortKeys = $tHashDepth ; if ($compactDump && !grep(ref $_, values %{$v})) { #$short = $sp . # (join ', ', # Next row core dumps during require from DB on 5.000, even with map {"_"} # map {&stringify($_) . " => " . &stringify($v->{$_})} # @sortKeys) . "'$shortmore"; $short = $sp; my @keys; for (@sortKeys) { push @keys, &stringify($_) . " => " . &stringify($v->{$_}); } $short .= join ', ', @keys; $short .= $shortmore; (print "$short\n"), return if length $short <= $compactDump; } for $key (@sortKeys) { return if $DB::signal; $value = $ {$v}{$key} ; print "$sp", &stringify($key), " => "; DumpElem $value, $s, $m-1; } print "$sp empty hash\n" unless @sortKeys; print "$sp$more" if defined $more ; } elsif ( $item_type eq 'ARRAY' ) { # Array ref or array-based object. Also: undef. # See how big the array is. $tArrayDepth = $#{$v} ; undef $more ; # Bigger than the max? $tArrayDepth = $#{$v} < $arrayDepth-1 ? $#{$v} : $arrayDepth-1 if defined $arrayDepth && $arrayDepth ne ''; # Yep. Don't show it all. $more = "....\n" if $tArrayDepth < $#{$v} ; $shortmore = ""; $shortmore = " ..." if $tArrayDepth < $#{$v} ; if ($compactDump && !grep(ref $_, @{$v})) { if ($#$v >= 0) { $short = $sp . "0..$#{$v} " . join(" ", map {exists $v->[$_] ? stringify $v->[$_] : "empty"} (0..$tArrayDepth) ) . "$shortmore"; } else { $short = $sp . "empty array"; } (print "$short\n"), return if length $short <= $compactDump; } #if ($compactDump && $short = ShortArray($v)) { # print "$short\n"; # return; #} for $num (0 .. $tArrayDepth) { return if $DB::signal; print "$sp$num "; if (exists $v->[$num]) { if (defined $v->[$num]) { DumpElem $v->[$num], $s, $m-1; } else { print "undef\n"; } } else { print "empty slot\n"; } } print "$sp empty array\n" unless @$v; print "$sp$more" if defined $more ; } elsif ( $item_type eq 'SCALAR' ) { unless (defined $$v) { print "$sp-> undef\n"; return; } print "$sp-> "; DumpElem $$v, $s, $m-1; } elsif ( $item_type eq 'REF' ) { print "$sp-> $$v\n"; return unless defined $$v; unwrap($$v, $s+3, $m-1); } elsif ( $item_type eq 'CODE' ) { # Code object or reference. print "$sp-> "; dumpsub (0, $v); } elsif ( $item_type eq 'GLOB' ) { # Glob object or reference. print "$sp-> ",&stringify($$v,1),"\n"; if ($globPrint) { $s += 3; dumpglob($s, "{$$v}", $$v, 1, $m-1); } elsif (defined ($fileno = eval {fileno($v)})) { print( (' ' x ($s+3)) . "FileHandle({$$v}) => fileno($fileno)\n" ); } } elsif (ref \$v eq 'GLOB') { # Raw glob (again?) if ($globPrint) { dumpglob($s, "{$v}", $v, 1, $m-1) if $globPrint; } elsif (defined ($fileno = eval {fileno(\$v)})) { print( (' ' x $s) . "FileHandle({$v}) => fileno($fileno)\n" ); } } }; if ($@) { print( (' ' x $s) . "<< value could not be dumped: $@ >>\n"); } return; } sub matchlex { (my $var = $_[0]) =~ s/.//; $var eq $_[1] or ($_[1] =~ /^([!~])(.)([\x00-\xff]*)/) and ($1 eq '!') ^ (eval { $var =~ /$2$3/ }); } sub matchvar { $_[0] eq $_[1] or ($_[1] =~ /^([!~])(.)([\x00-\xff]*)/) and ($1 eq '!') ^ (eval {($_[2] . "::" . $_[0]) =~ /$2$3/}); } sub compactDump { $compactDump = shift if @_; $compactDump = 6*80-1 if $compactDump and $compactDump < 2; $compactDump; } sub veryCompact { $veryCompact = shift if @_; compactDump(1) if !$compactDump and $veryCompact; $veryCompact; } sub unctrlSet { if (@_) { my $in = shift; if ($in eq 'unctrl' or $in eq 'quote') { $unctrl = $in; } else { print "Unknown value for 'unctrl'.\n"; } } $unctrl; } sub quote { if (@_ and $_[0] eq '"') { $tick = '"'; $unctrl = 'quote'; } elsif (@_ and $_[0] eq 'auto') { $tick = 'auto'; $unctrl = 'quote'; } elsif (@_) { # Need to set $tick = "'"; $unctrl = 'unctrl'; } $tick; } sub dumpglob { return if $DB::signal; my ($off,$key, $val, $all, $m) = @_; local(*entry) = $val; my $fileno; if (($key !~ /^_ fileno($fileno)\n" ); } if ($all) { if (defined &entry) { dumpsub($off, $key); } } } sub dumplex { return if $DB::signal; my ($key, $val, $m, @vars) = @_; return if @vars && !grep( matchlex($key, $_), @vars ); local %address; my $off = 0; # It reads better this way my $fileno; if (UNIVERSAL::isa($val,'ARRAY')) { print( (' ' x $off) . "$key = (\n" ); unwrap($val,3+$off,$m) ; print( (' ' x $off) . ")\n" ); } elsif (UNIVERSAL::isa($val,'HASH')) { print( (' ' x $off) . "$key = (\n" ); unwrap($val,3+$off,$m) ; print( (' ' x $off) . ")\n" ); } elsif (UNIVERSAL::isa($val,'IO')) { print( (' ' x $off) . "FileHandle($key) => fileno($fileno)\n" ); } # No lexical subroutines yet... # elsif (UNIVERSAL::isa($val,'CODE')) { # dumpsub($off, $$val); # } else { print( (' ' x $off) . &unctrl($key), " = " ); DumpElem $$val, 3+$off, $m; } } sub CvGV_name_or_bust { my $in = shift; return if $skipCvGV; # Backdoor to avoid problems if XS broken... $in = \&$in; # Hard reference... eval {require Devel::Peek; 1} or return; my $gv = Devel::Peek::CvGV($in) or return; *$gv{PACKAGE} . '::' . *$gv{NAME}; } sub dumpsub { my ($off,$sub) = @_; my $ini = $sub; my $s; $sub = $1 if $sub =~ /^\{\*(.*)\}$/; my $subref = defined $1 ? \&$sub : \&$ini; my $place = $DB::sub{$sub} || (($s = $subs{"$subref"}) && $DB::sub{$s}) || (($s = CvGV_name_or_bust($subref)) && $DB::sub{$s}) || ($subdump && ($s = findsubs("$subref")) && $DB::sub{$s}); $place = '???' unless defined $place; $s = $sub unless defined $s; print( (' ' x $off) . "&$s in $place\n" ); } sub findsubs { return undef unless %DB::sub; my ($addr, $name, $loc); while (($name, $loc) = each %DB::sub) { $addr = \&$name; $subs{"$addr"} = $name; } $subdump = 0; $subs{ shift() }; } sub main::dumpvar { my ($package,$m,@vars) = @_; local(%address,$key,$val,$^W); $package .= "::" unless $package =~ /::$/; *stab = *{"main::"}; while ($package =~ /(\w+?::)/g){ *stab = $ {stab}{$1}; } local $TotalStrings = 0; local $Strings = 0; local $CompleteTotal = 0; while (($key,$val) = each(%stab)) { return if $DB::signal; next if @vars && !grep( matchvar($key, $_), @vars ); if ($usageOnly) { globUsage(\$val, $key) if ($package ne 'dumpvar' or $key ne 'stab') and ref(\$val) eq 'GLOB'; } else { dumpglob(0,$key, $val, 0, $m); } } if ($usageOnly) { print "String space: $TotalStrings bytes in $Strings strings.\n"; $CompleteTotal += $TotalStrings; print "Grand total = $CompleteTotal bytes (1 level deep) + overhead.\n"; } } sub scalarUsage { my $size = length($_[0]); $TotalStrings += $size; $Strings++; $size; } sub arrayUsage { # array ref, name my $size = 0; map {$size += scalarUsage($_)} @{$_[0]}; my $len = @{$_[0]}; print "\@$_[1] = $len item", ($len > 1 ? "s" : ""), " (data: $size bytes)\n" if defined $_[1]; $CompleteTotal += $size; $size; } sub hashUsage { # hash ref, name my @keys = keys %{$_[0]}; my @values = values %{$_[0]}; my $keys = arrayUsage \@keys; my $values = arrayUsage \@values; my $len = @keys; my $total = $keys + $values; print "\%$_[1] = $len item", ($len > 1 ? "s" : ""), " (keys: $keys; values: $values; total: $total bytes)\n" if defined $_[1]; $total; } sub globUsage { # glob ref, name local *name = *{$_[0]}; $total = 0; $total += scalarUsage $name if defined $name; $total += arrayUsage \@name, $_[1] if @name; $total += hashUsage \%name, $_[1] if %name and $_[1] ne "main::" and $_[1] ne "DB::"; #and !($package eq "dumpvar" and $key eq "stab")); $total; } sub packageUsage { my ($package,@vars) = @_; $package .= "::" unless $package =~ /::$/; local *stab = *{"main::"}; while ($package =~ /(\w+?::)/g){ *stab = $ {stab}{$1}; } local $TotalStrings = 0; local $CompleteTotal = 0; my ($key,$val); while (($key,$val) = each(%stab)) { next if @vars && !grep($key eq $_,@vars); globUsage \$val, $key unless $package eq 'dumpvar' and $key eq 'stab'; } print "String space: $TotalStrings.\n"; $CompleteTotal += $TotalStrings; print "\nGrand total = $CompleteTotal bytes\n"; } 1; package deprecate; use strict; use warnings; our $VERSION = 0.03; # our %Config can ignore %Config::Config, e.g. for testing our %Config; unless (%Config) { require Config; *Config = \%Config::Config; } # This isn't a public API. It's internal to code maintained by the perl-porters # If you would like it to be a public API, please send a patch with # documentation and tests. Until then, it may change without warning. sub __loaded_from_core { my ($package, $file, $expect_leaf) = @_; foreach my $pair ([qw(sitearchexp archlibexp)], [qw(sitelibexp privlibexp)]) { my ($site, $priv) = @Config{@$pair}; if ($^O eq 'VMS') { for my $d ($site, $priv) { $d = VMS::Filespec::unixify($d) }; } # Just in case anyone managed to configure with trailing /s s!/*$!!g foreach $site, $priv; next if $site eq $priv; if (uc("$priv/$expect_leaf") eq uc($file)) { return 1; } } return 0; } sub import { my ($package, $file) = caller; my $expect_leaf = "$package.pm"; $expect_leaf =~ s!::!/!g; if (__loaded_from_core($package, $file, $expect_leaf)) { my $call_depth=1; my @caller; while (@caller = caller $call_depth++) { last if $caller[7] # use/require and $caller[6] eq $expect_leaf; # the package file } unless (@caller) { require Carp; Carp::cluck(<<"EOM"); Can't find use/require $expect_leaf in caller stack EOM return; } # This is fragile, because it # is directly poking in the internals of warnings.pm my ($call_file, $call_line, $callers_bitmask) = @caller[1,2,9]; if (defined $callers_bitmask && (vec($callers_bitmask, $warnings::Offsets{deprecated}, 1) || vec($callers_bitmask, $warnings::Offsets{all}, 1))) { warn <<"EOM"; $package will be removed from the Perl core distribution in the next major release. Please install it from CPAN. It is being used at $call_file, line $call_line. EOM } } } 1; __END__ package AutoSplit; use Exporter (); use Config qw(%Config); use File::Basename (); use File::Path qw(mkpath); use File::Spec::Functions qw(curdir catfile catdir); use strict; our($VERSION, @ISA, @EXPORT, @EXPORT_OK, $Verbose, $Keep, $Maxlen, $CheckForAutoloader, $CheckModTime); $VERSION = "1.06"; @ISA = qw(Exporter); @EXPORT = qw(&autosplit &autosplit_lib_modules); @EXPORT_OK = qw($Verbose $Keep $Maxlen $CheckForAutoloader $CheckModTime); # for portability warn about names longer than $maxlen $Maxlen = 8; # 8 for dos, 11 (14-".al") for SYSVR3 $Verbose = 1; # 0=none, 1=minimal, 2=list .al files $Keep = 0; $CheckForAutoloader = 1; $CheckModTime = 1; my $IndexFile = "autosplit.ix"; # file also serves as timestamp my $maxflen = 255; $maxflen = 14 if $Config{'d_flexfnam'} ne 'define'; if (defined (&Dos::UseLFN)) { $maxflen = Dos::UseLFN() ? 255 : 11; } my $Is_VMS = ($^O eq 'VMS'); # allow checking for valid ': attrlist' attachments. # extra jugglery required to support both 5.8 and 5.9/5.10 features # (support for 5.8 required for cross-compiling environments) my $attr_list = $] >= 5.009005 ? eval <<'__QR__' qr{ \s* : \s* (?: # one attribute (?> # no backtrack (?! \d) \w+ (? \( (?: [^()]++ | (?&nested)++ )*+ \) ) ? ) (?: \s* : \s* | \s+ (?! :) ) )* }x __QR__ : do { # In pre-5.9.5 world we have to do dirty tricks. # (we use 'our' rather than 'my' here, due to the rather complex and buggy # behaviour of lexicals with qr// and (??{$lex}) ) our $trick1; # yes, cannot our and assign at the same time. $trick1 = qr{ \( (?: (?> [^()]+ ) | (??{ $trick1 }) )* \) }x; our $trick2 = qr{ (?> (?! \d) \w+ (?:$trick1)? ) (?:\s*\:\s*|\s+(?!\:)) }x; qr{ \s* : \s* (?: $trick2 )* }x; }; sub autosplit{ my($file, $autodir, $keep, $ckal, $ckmt) = @_; # $file - the perl source file to be split (after __END__) # $autodir - the ".../auto" dir below which to write split subs # Handle optional flags: $keep = $Keep unless defined $keep; $ckal = $CheckForAutoloader unless defined $ckal; $ckmt = $CheckModTime unless defined $ckmt; autosplit_file($file, $autodir, $keep, $ckal, $ckmt); } sub carp{ require Carp; goto &Carp::carp; } # This function is used during perl building/installation # ./miniperl -e 'use AutoSplit; autosplit_lib_modules(@ARGV)' ... sub autosplit_lib_modules { my(@modules) = @_; # list of Module names local $_; # Avoid clobber. while (defined($_ = shift @modules)) { while (m#([^:]+)::([^:].*)#) { # in case specified as ABC::XYZ $_ = catfile($1, $2); } s|\\|/|g; # bug in ksh OS/2 s#^lib/##s; # incase specified as lib/*.pm my($lib) = catfile(curdir(), "lib"); if ($Is_VMS) { # may need to convert VMS-style filespecs $lib =~ s#^\[\]#.\/#; } s#^$lib\W+##s; # incase specified as ./lib/*.pm if ($Is_VMS && /[:>\]]/) { # may need to convert VMS-style filespecs my ($dir,$name) = (/(.*])(.*)/s); $dir =~ s/.*lib[\.\]]//s; $dir =~ s#[\.\]]#/#g; $_ = $dir . $name; } autosplit_file(catfile($lib, $_), catfile($lib, "auto"), $Keep, $CheckForAutoloader, $CheckModTime); } 0; } # private functions my $self_mod_time = (stat __FILE__)[9]; sub autosplit_file { my($filename, $autodir, $keep, $check_for_autoloader, $check_mod_time) = @_; my(@outfiles); local($_); local($/) = "\n"; # where to write output files $autodir ||= catfile(curdir(), "lib", "auto"); if ($Is_VMS) { ($autodir = VMS::Filespec::unixpath($autodir)) =~ s|/\z||; $filename = VMS::Filespec::unixify($filename); # may have dirs } unless (-d $autodir){ mkpath($autodir,0,0755); # We should never need to create the auto dir # here. installperl (or similar) should have done # it. Expecting it to exist is a valuable sanity check against # autosplitting into some random directory by mistake. print "Warning: AutoSplit had to create top-level " . "$autodir unexpectedly.\n"; } # allow just a package name to be used $filename .= ".pm" unless ($filename =~ m/\.pm\z/); open(my $in, "<$filename") or die "AutoSplit: Can't open $filename: $!\n"; my($pm_mod_time) = (stat($filename))[9]; my($autoloader_seen) = 0; my($in_pod) = 0; my($def_package,$last_package,$this_package,$fnr); while (<$in>) { # Skip pod text. $fnr++; $in_pod = 1 if /^=\w/; $in_pod = 0 if /^=cut/; next if ($in_pod || /^=cut/); next if /^\s*#/; # record last package name seen $def_package = $1 if (m/^\s*package\s+([\w:]+)\s*;/); ++$autoloader_seen if m/^\s*(use|require)\s+AutoLoader\b/; ++$autoloader_seen if m/\bISA\s*=.*\bAutoLoader\b/; last if /^__END__/; } if ($check_for_autoloader && !$autoloader_seen){ print "AutoSplit skipped $filename: no AutoLoader used\n" if ($Verbose>=2); return 0; } $_ or die "Can't find __END__ in $filename\n"; $def_package or die "Can't find 'package Name;' in $filename\n"; my($modpname) = _modpname($def_package); # this _has_ to match so we have a reasonable timestamp file die "Package $def_package ($modpname.pm) does not ". "match filename $filename" unless ($filename =~ m/\Q$modpname.pm\E$/ or ($^O eq 'dos') or ($^O eq 'MSWin32') or ($^O eq 'NetWare') or $Is_VMS && $filename =~ m/$modpname.pm/i); my($al_idx_file) = catfile($autodir, $modpname, $IndexFile); if ($check_mod_time){ my($al_ts_time) = (stat("$al_idx_file"))[9] || 1; if ($al_ts_time >= $pm_mod_time and $al_ts_time >= $self_mod_time){ print "AutoSplit skipped ($al_idx_file newer than $filename)\n" if ($Verbose >= 2); return undef; # one undef, not a list } } my($modnamedir) = catdir($autodir, $modpname); print "AutoSplitting $filename ($modnamedir)\n" if $Verbose; unless (-d $modnamedir){ mkpath($modnamedir,0,0777); } # We must try to deal with some SVR3 systems with a limit of 14 # characters for file names. Sadly we *cannot* simply truncate all # file names to 14 characters on these systems because we *must* # create filenames which exactly match the names used by AutoLoader.pm. # This is a problem because some systems silently truncate the file # names while others treat long file names as an error. my $Is83 = $maxflen==11; # plain, case INSENSITIVE dos filenames my(@subnames, $subname, %proto, %package); my @cache = (); my $caching = 1; $last_package = ''; my $out; while (<$in>) { $fnr++; $in_pod = 1 if /^=\w/; $in_pod = 0 if /^=cut/; next if ($in_pod || /^=cut/); # the following (tempting) old coding gives big troubles if a # cut is forgotten at EOF: # next if /^=\w/ .. /^=cut/; if (/^package\s+([\w:]+)\s*;/) { $this_package = $def_package = $1; } if (/^sub\s+([\w:]+)(\s*(?:\(.*?\))?(?:$attr_list)?)/) { print $out "# end of $last_package\::$subname\n1;\n" if $last_package; $subname = $1; my $proto = $2 || ''; if ($subname =~ s/(.*):://){ $this_package = $1; } else { $this_package = $def_package; } my $fq_subname = "$this_package\::$subname"; $package{$fq_subname} = $this_package; $proto{$fq_subname} = $proto; push(@subnames, $fq_subname); my($lname, $sname) = ($subname, substr($subname,0,$maxflen-3)); $modpname = _modpname($this_package); my($modnamedir) = catdir($autodir, $modpname); mkpath($modnamedir,0,0777); my($lpath) = catfile($modnamedir, "$lname.al"); my($spath) = catfile($modnamedir, "$sname.al"); my $path; if (!$Is83 and open($out, ">$lpath")){ $path=$lpath; print " writing $lpath\n" if ($Verbose>=2); } else { open($out, ">$spath") or die "Can't create $spath: $!\n"; $path=$spath; print " writing $spath (with truncated name)\n" if ($Verbose>=1); } push(@outfiles, $path); my $lineno = $fnr - @cache; print $out < lc($_) } @outfiles; } else { @outfiles{@outfiles} = @outfiles; } my(%outdirs,@outdirs); for (@outfiles) { $outdirs{File::Basename::dirname($_)}||=1; } for my $dir (keys %outdirs) { opendir(my $outdir,$dir); foreach (sort readdir($outdir)){ next unless /\.al\z/; my($file) = catfile($dir, $_); $file = lc $file if $Is83 or $Is_VMS; next if $outfiles{$file}; print " deleting $file\n" if ($Verbose>=2); my($deleted,$thistime); # catch all versions on VMS do { $deleted += ($thistime = unlink $file) } while ($thistime); carp ("Unable to delete $file: $!") unless $deleted; } closedir($outdir); } } open(my $ts,">$al_idx_file") or carp ("AutoSplit: unable to create timestamp file ($al_idx_file): $!"); print $ts "# Index created by AutoSplit for $filename\n"; print $ts "# (file acts as timestamp)\n"; $last_package = ''; for my $fqs (@subnames) { my($subname) = $fqs; $subname =~ s/.*:://; print $ts "package $package{$fqs};\n" unless $last_package eq $package{$fqs}; print $ts "sub $subname $proto{$fqs};\n"; $last_package = $package{$fqs}; } print $ts "1;\n"; close($ts); _check_unique($filename, $Maxlen, 1, @outfiles); @outfiles; } sub _modpname ($) { my($package) = @_; my $modpname = $package; if ($^O eq 'MSWin32') { $modpname =~ s#::#\\#g; } else { my @modpnames = (); while ($modpname =~ m#(.*?[^:])::([^:].*)#) { push @modpnames, $1; $modpname = $2; } $modpname = catfile(@modpnames, $modpname); } if ($Is_VMS) { $modpname = VMS::Filespec::unixify($modpname); # may have dirs } $modpname; } sub _check_unique { my($filename, $maxlen, $warn, @outfiles) = @_; my(%notuniq) = (); my(%shorts) = (); my(@toolong) = grep( length(File::Basename::basename($_)) > $maxlen, @outfiles ); foreach (@toolong){ my($dir) = File::Basename::dirname($_); my($file) = File::Basename::basename($_); my($trunc) = substr($file,0,$maxlen); $notuniq{$dir}{$trunc} = 1 if $shorts{$dir}{$trunc}; $shorts{$dir}{$trunc} = $shorts{$dir}{$trunc} ? "$shorts{$dir}{$trunc}, $file" : $file; } if (%notuniq && $warn){ print "$filename: some names are not unique when " . "truncated to $maxlen characters:\n"; foreach my $dir (sort keys %notuniq){ print " directory $dir:\n"; foreach my $trunc (sort keys %{$notuniq{$dir}}) { print " $shorts{$dir}{$trunc} truncate to $trunc\n"; } } } } 1; __END__ # test functions so AutoSplit.pm can be applied to itself: sub test1 ($) { "test 1\n"; } sub test2 ($$) { "test 2\n"; } sub test3 ($$$) { "test 3\n"; } sub testtesttesttest4_1 { "test 4\n"; } sub testtesttesttest4_2 { "duplicate test 4\n"; } sub Just::Another::test5 { "another test 5\n"; } sub test6 { return join ":", __FILE__,__LINE__; } package Yet::Another::AutoSplit; sub testtesttesttest4_1 ($) { "another test 4\n"; } sub testtesttesttest4_2 ($$) { "another duplicate test 4\n"; } package Yet::More::Attributes; sub test_a1 ($) : locked :locked { 1; } sub test_a2 : locked { 1; } package diagnostics; use strict; use 5.009001; use Carp; $Carp::Internal{__PACKAGE__.""}++; our $VERSION = '1.36'; our $DEBUG; our $VERBOSE; our $PRETTY; our $TRACEONLY = 0; our $WARNTRACE = 0; use Config; use Text::Tabs 'expand'; my $privlib = $Config{privlibexp}; if ($^O eq 'VMS') { require VMS::Filespec; $privlib = VMS::Filespec::unixify($privlib); } my @trypod = ( "$privlib/pod/perldiag.pod", "$privlib/pods/perldiag.pod", ); # handy for development testing of new warnings etc unshift @trypod, "./pod/perldiag.pod" if -e "pod/perldiag.pod"; (my $PODFILE) = ((grep { -e } @trypod), $trypod[$#trypod])[0]; $DEBUG ||= 0; local $| = 1; local $_; local $.; my $standalone; my(%HTML_2_Troff, %HTML_2_Latin_1, %HTML_2_ASCII_7); CONFIG: { our $opt_p = our $opt_d = our $opt_v = our $opt_f = ''; unless (caller) { $standalone++; require Getopt::Std; Getopt::Std::getopts('pdvf:') or die "Usage: $0 [-v] [-p] [-f splainpod]"; $PODFILE = $opt_f if $opt_f; $DEBUG = 2 if $opt_d; $VERBOSE = $opt_v; $PRETTY = $opt_p; } if (open(POD_DIAG, '<', $PODFILE)) { warn "Happy happy podfile from real $PODFILE\n" if $DEBUG; last CONFIG; } if (caller) { INCPATH: { for my $file ( (map { "$_/".__PACKAGE__.".pm" } @INC), $0) { warn "Checking $file\n" if $DEBUG; if (open(POD_DIAG, '<', $file)) { while () { next unless /^__END__\s*# wish diag dbase were more accessible/; print STDERR "podfile is $file\n" if $DEBUG; last INCPATH; } } } } } else { print STDERR "podfile is \n" if $DEBUG; *POD_DIAG = *main::DATA; } } if (eof(POD_DIAG)) { die "couldn't find diagnostic data in $PODFILE @INC $0"; } %HTML_2_Troff = ( 'amp' => '&', # ampersand 'lt' => '<', # left chevron, less-than 'gt' => '>', # right chevron, greater-than 'quot' => '"', # double quote "Aacute" => "A\\*'", # capital A, acute accent # etc ); %HTML_2_Latin_1 = ( 'amp' => '&', # ampersand 'lt' => '<', # left chevron, less-than 'gt' => '>', # right chevron, greater-than 'quot' => '"', # double quote "Aacute" => "\xC1" # capital A, acute accent # etc ); %HTML_2_ASCII_7 = ( 'amp' => '&', # ampersand 'lt' => '<', # left chevron, less-than 'gt' => '>', # right chevron, greater-than 'quot' => '"', # double quote "Aacute" => "A" # capital A, acute accent # etc ); our %HTML_Escapes; *HTML_Escapes = do { if ($standalone) { $PRETTY ? \%HTML_2_Latin_1 : \%HTML_2_ASCII_7; } else { \%HTML_2_Latin_1; } }; *THITHER = $standalone ? *STDOUT : *STDERR; my %transfmt = (); my $transmo = <) { sub _split_pod_link { $_[0] =~ m'(?:([^|]*)\|)?([^/]*)(?:/("?)(.*)\3)?'s; ($1,$2,$4); } unescape(); if ($PRETTY) { sub noop { return $_[0] } # spensive for a noop sub bold { my $str =$_[0]; $str =~ s/(.)/$1\b$1/g; return $str; } sub italic { my $str = $_[0]; $str =~ s/(.)/_\b$1/g; return $str; } s/C<<< (.*?) >>>|C<< (.*?) >>|[BC]<(.*?)>/bold($+)/ges; s/[IF]<(.*?)>/italic($1)/ges; s/L<(.*?)>/ my($text,$page,$sect) = _split_pod_link($1); defined $text ? $text : defined $sect ? italic($sect) . ' in ' . italic($page) : italic($page) /ges; s/S<(.*?)>/ $1 /ges; } else { s/C<<< (.*?) >>>|C<< (.*?) >>|[BC]<(.*?)>/$+/gs; s/[IF]<(.*?)>/$1/gs; s/L<(.*?)>/ my($text,$page,$sect) = _split_pod_link($1); defined $text ? $text : defined $sect ? qq '"$sect" in $page' : $page /ges; s/S<(.*?)>/ $1 /ges; } unless (/^=/) { if (defined $header) { if ( $header eq 'DESCRIPTION' && ( /Optional warnings are enabled/ || /Some of these messages are generic./ ) ) { next; } $_ = expand $_; s/^/ /gm; $msg{$header} .= $_; for my $h(@headers) { $msg{$h} .= $_ } ++$seen_body; undef $for_item; } next; } # If we have not come across the body of the description yet, then # the previous header needs to share the same description. if ($seen_body) { @headers = (); } else { push @headers, $header if defined $header; } if ( ! s/=item (.*?)\s*\z//s || $over_level != 1) { if ( s/=head1\sDESCRIPTION//) { $msg{$header = 'DESCRIPTION'} = ''; undef $for_item; } elsif( s/^=for\s+diagnostics\s*\n(.*?)\s*\z// ) { $for_item = $1; } elsif( /^=over\b/ ) { $over_level++; } elsif( /^=back\b/ ) { # Stop processing body here $over_level--; if ($over_level == 0) { undef $header; undef $for_item; $seen_body = 0; next; } } next; } if( $for_item ) { $header = $for_item; undef $for_item } else { $header = $1; $header =~ s/\n/ /gs; # Allow multi-line headers } # strip formatting directives from =item line $header =~ s/[A-Z]<(.*?)>/$1/g; # Since we strip "(\.\s*)\n" when we search a warning, strip it here as well $header =~ s/(\.\s*)?$//; my @toks = split( /(%l?[dxX]|%[ucp]|%(?:\.\d+)?[fs])/, $header ); if (@toks > 1) { my $conlen = 0; for my $i (0..$#toks){ if( $i % 2 ){ if( $toks[$i] eq '%c' ){ $toks[$i] = '.'; } elsif( $toks[$i] =~ /^%(?:d|u)$/ ){ $toks[$i] = '\d+'; } elsif( $toks[$i] =~ '^%(?:s|.*f)$' ){ $toks[$i] = $i == $#toks ? '.*' : '.*?'; } elsif( $toks[$i] =~ '%.(\d+)s' ){ $toks[$i] = ".{$1}"; } elsif( $toks[$i] =~ '^%l*([pxX])$' ){ $toks[$i] = $1 eq 'X' ? '[\dA-F]+' : '[\da-f]+'; } } elsif( length( $toks[$i] ) ){ $toks[$i] = quotemeta $toks[$i]; $conlen += length( $toks[$i] ); } } my $lhs = join( '', @toks ); $lhs =~ s/(\\\s)+/\\s+/g; # Replace lit space with multi-space match $transfmt{$header}{pat} = " s^\\s*$lhs\\s*\Q$header\Es\n\t&& return 1;\n"; $transfmt{$header}{len} = $conlen; } else { my $lhs = "\Q$header\E"; $lhs =~ s/(\\\s)+/\\s+/g; # Replace lit space with multi-space match $transfmt{$header}{pat} = " s^\\s*$lhs\\s*\Q$header\E\n\t && return 1;\n"; $transfmt{$header}{len} = length( $header ); } print STDERR __PACKAGE__.": Duplicate entry: \"$header\"\n" if $msg{$header}; $msg{$header} = ''; $seen_body = 0; } close POD_DIAG unless *main::DATA eq *POD_DIAG; die "No diagnostics?" unless %msg; # Apply patterns in order of decreasing sum of lengths of fixed parts # Seems the best way of hitting the right one. for my $hdr ( sort { $transfmt{$b}{len} <=> $transfmt{$a}{len} } keys %transfmt ){ $transmo .= $transfmt{$hdr}{pat}; } $transmo .= " return 0;\n}\n"; print STDERR $transmo if $DEBUG; eval $transmo; die $@ if $@; } if ($standalone) { if (!@ARGV and -t STDIN) { print STDERR "$0: Reading from STDIN\n" } while (defined (my $error = <>)) { splainthis($error) || print THITHER $error; } exit; } my $olddie; my $oldwarn; sub import { shift; $^W = 1; # yup, clobbered the global variable; # tough, if you want diags, you want diags. return if defined $SIG{__WARN__} && ($SIG{__WARN__} eq \&warn_trap); for (@_) { /^-d(ebug)?$/ && do { $DEBUG++; next; }; /^-v(erbose)?$/ && do { $VERBOSE++; next; }; /^-p(retty)?$/ && do { print STDERR "$0: I'm afraid it's too late for prettiness.\n"; $PRETTY++; next; }; # matches trace and traceonly for legacy doc mixup reasons /^-t(race(only)?)?$/ && do { $TRACEONLY++; next; }; /^-w(arntrace)?$/ && do { $WARNTRACE++; next; }; warn "Unknown flag: $_"; } $oldwarn = $SIG{__WARN__}; $olddie = $SIG{__DIE__}; $SIG{__WARN__} = \&warn_trap; $SIG{__DIE__} = \&death_trap; } sub enable { &import } sub disable { shift; return unless $SIG{__WARN__} eq \&warn_trap; $SIG{__WARN__} = $oldwarn || ''; $SIG{__DIE__} = $olddie || ''; } sub warn_trap { my $warning = $_[0]; if (caller eq __PACKAGE__ or !splainthis($warning)) { if ($WARNTRACE) { print STDERR Carp::longmess($warning); } else { print STDERR $warning; } } goto &$oldwarn if defined $oldwarn and $oldwarn and $oldwarn ne \&warn_trap; }; sub death_trap { my $exception = $_[0]; # See if we are coming from anywhere within an eval. If so we don't # want to explain the exception because it's going to get caught. my $in_eval = 0; my $i = 0; while (my $caller = (caller($i++))[3]) { if ($caller eq '(eval)') { $in_eval = 1; last; } } splainthis($exception) unless $in_eval; if (caller eq __PACKAGE__) { print STDERR "INTERNAL EXCEPTION: $exception"; } &$olddie if defined $olddie and $olddie and $olddie ne \&death_trap; return if $in_eval; # We don't want to unset these if we're coming from an eval because # then we've turned off diagnostics. # Switch off our die/warn handlers so we don't wind up in our own # traps. $SIG{__DIE__} = $SIG{__WARN__} = ''; $exception =~ s/\n(?=.)/\n\t/gas; die Carp::longmess("__diagnostics__") =~ s/^__diagnostics__.*?line \d+\.?\n/ "Uncaught exception from user code:\n\t$exception" /re; # up we go; where we stop, nobody knows, but i think we die now # but i'm deeply afraid of the &$olddie guy reraising and us getting # into an indirect recursion loop }; my %exact_duplicate; my %old_diag; my $count; my $wantspace; sub splainthis { return 0 if $TRACEONLY; for (my $tmp = shift) { local $\; local $!; ### &finish_compilation unless %msg; s/(\.\s*)?\n+$//; my $orig = $_; # return unless defined; # get rid of the where-are-we-in-input part s/, <.*?> (?:line|chunk).*$//; # Discard 1st " at line " and all text beyond # but be aware of messages containing " at this-or-that" my $real = 0; my @secs = split( / at / ); return unless @secs; $_ = $secs[0]; for my $i ( 1..$#secs ){ if( $secs[$i] =~ /.+? (?:line|chunk) \d+/ ){ $real = 1; last; } else { $_ .= ' at ' . $secs[$i]; } } # remove parenthesis occurring at the end of some messages s/^\((.*)\)$/$1/; if ($exact_duplicate{$orig}++) { return &transmo; } else { return 0 unless &transmo; } my $short = shorten($orig); if ($old_diag{$_}) { autodescribe(); print THITHER "$short (#$old_diag{$_})\n"; $wantspace = 1; } elsif (!$msg{$_} && $orig =~ /\n./s) { # A multiline message, like "Attempt to reload / # Compilation failed" my $found; for (split /^/, $orig) { splainthis($_) and $found = 1; } return $found; } else { autodescribe(); $old_diag{$_} = ++$count; print THITHER "\n" if $wantspace; $wantspace = 0; print THITHER "$short (#$old_diag{$_})\n"; if ($msg{$_}) { print THITHER $msg{$_}; } else { if (0 and $standalone) { print THITHER " **** Error #$old_diag{$_} ", ($real ? "is" : "appears to be"), " an unknown diagnostic message.\n\n"; } return 0; } } return 1; } } sub autodescribe { if ($VERBOSE and not $count) { print THITHER &{$PRETTY ? \&bold : \&noop}("DESCRIPTION OF DIAGNOSTICS"), "\n$msg{DESCRIPTION}\n"; } } sub unescape { s { E< ( [A-Za-z]+ ) > } { do { exists $HTML_Escapes{$1} ? do { $HTML_Escapes{$1} } : do { warn "Unknown escape: E<$1> in $_"; "E<$1>"; } } }egx; } sub shorten { my $line = $_[0]; if (length($line) > 79 and index($line, "\n") == -1) { my $space_place = rindex($line, ' ', 79); if ($space_place != -1) { substr($line, $space_place, 1) = "\n\t"; } } return $line; } 1 unless $standalone; # or it'll complain about itself __END__ # wish diag dbase were more accessible package ok; our $VERSION = '1.302133'; use strict; use Test::More (); sub import { shift; if (@_) { goto &Test::More::pass if $_[0] eq 'ok'; goto &Test::More::use_ok; } # No argument list - croak as if we are prototyped like use_ok() my (undef, $file, $line) = caller(); ($file =~ /^\(eval/) or die "Not enough arguments for 'use ok' at $file line $line\n"; } __END__ package Safe; use 5.003_11; use Scalar::Util qw(reftype refaddr); $Safe::VERSION = "2.40"; # *** Don't declare any lexicals above this point *** # # This function should return a closure which contains an eval that can't # see any lexicals in scope (apart from __ExPr__ which is unavoidable) sub lexless_anon_sub { # $_[0] is package; # $_[1] is strict flag; my $__ExPr__ = $_[2]; # must be a lexical to create the closure that # can be used to pass the value into the safe # world # Create anon sub ref in root of compartment. # Uses a closure (on $__ExPr__) to pass in the code to be executed. # (eval on one line to keep line numbers as expected by caller) eval sprintf 'package %s; %s sub { @_=(); eval q[local *SIG; my $__ExPr__;] . $__ExPr__; }', $_[0], $_[1] ? 'use strict;' : ''; } use strict; use Carp; BEGIN { eval q{ use Carp::Heavy; } } use B (); BEGIN { no strict 'refs'; if (defined &B::sub_generation) { *sub_generation = \&B::sub_generation; } else { # fake sub generation changing for perls < 5.8.9 my $sg; *sub_generation = sub { ++$sg }; } } use Opcode 1.01, qw( opset opset_to_ops opmask_add empty_opset full_opset invert_opset verify_opset opdesc opcodes opmask define_optag opset_to_hex ); *ops_to_opset = \&opset; # Temporary alias for old Penguins # Regular expressions and other unicode-aware code may need to call # utf8->SWASHNEW (via perl's utf8.c). That will fail unless we share the # SWASHNEW method. # Sadly we can't just add utf8::SWASHNEW to $default_share because perl's # utf8.c code does a fetchmethod on SWASHNEW to check if utf8.pm is loaded, # and sharing makes it look like the method exists. # The simplest and most robust fix is to ensure the utf8 module is loaded when # Safe is loaded. Then we can add utf8::SWASHNEW to $default_share. require utf8; # we must ensure that utf8_heavy.pl, where SWASHNEW is defined, is loaded # but without depending on too much knowledge of that implementation detail. # This code (//i on a unicode string) should ensure utf8 is fully loaded # and also loads the ToFold SWASH, unless things change so that these # particular code points don't cause it to load. # (Swashes are cached internally by perl in PL_utf8_* variables # independent of being inside/outside of Safe. So once loaded they can be) do { my $a = pack('U',0x100); my $b = chr 0x101; utf8::upgrade $b; $a =~ /$b/i }; # now we can safely include utf8::SWASHNEW in $default_share defined below. my $default_root = 0; # share *_ and functions defined in universal.c # Don't share stuff like *UNIVERSAL:: otherwise code from the # compartment can 0wn functions in UNIVERSAL my $default_share = [qw[ *_ &PerlIO::get_layers &UNIVERSAL::isa &UNIVERSAL::can &UNIVERSAL::VERSION &utf8::is_utf8 &utf8::valid &utf8::encode &utf8::decode &utf8::upgrade &utf8::downgrade &utf8::native_to_unicode &utf8::unicode_to_native &utf8::SWASHNEW $version::VERSION $version::CLASS $version::STRICT $version::LAX @version::ISA ], ($] < 5.010 && qw[ &utf8::SWASHGET ]), ($] >= 5.008001 && qw[ &Regexp::DESTROY ]), ($] >= 5.010 && qw[ &re::is_regexp &re::regname &re::regnames &re::regnames_count &UNIVERSAL::DOES &version::() &version::new &version::("" &version::stringify &version::(0+ &version::numify &version::normal &version::(cmp &version::(<=> &version::vcmp &version::(bool &version::boolean &version::(nomethod &version::noop &version::is_alpha &version::qv &version::vxs::declare &version::vxs::qv &version::vxs::_VERSION &version::vxs::stringify &version::vxs::new &version::vxs::parse &version::vxs::VCMP ]), ($] >= 5.011 && qw[ &re::regexp_pattern ]), ($] >= 5.010 && $] < 5.014 && qw[ &Tie::Hash::NamedCapture::FETCH &Tie::Hash::NamedCapture::STORE &Tie::Hash::NamedCapture::DELETE &Tie::Hash::NamedCapture::CLEAR &Tie::Hash::NamedCapture::EXISTS &Tie::Hash::NamedCapture::FIRSTKEY &Tie::Hash::NamedCapture::NEXTKEY &Tie::Hash::NamedCapture::SCALAR &Tie::Hash::NamedCapture::flags ])]; if (defined $Devel::Cover::VERSION) { push @$default_share, '&Devel::Cover::use_file'; } sub new { my($class, $root, $mask) = @_; my $obj = {}; bless $obj, $class; if (defined($root)) { croak "Can't use \"$root\" as root name" if $root =~ /^main\b/ or $root !~ /^\w[:\w]*$/; $obj->{Root} = $root; $obj->{Erase} = 0; } else { $obj->{Root} = "Safe::Root".$default_root++; $obj->{Erase} = 1; } # use permit/deny methods instead till interface issues resolved # XXX perhaps new Safe 'Root', mask => $mask, foo => bar, ...; croak "Mask parameter to new no longer supported" if defined $mask; $obj->permit_only(':default'); # We must share $_ and @_ with the compartment or else ops such # as split, length and so on won't default to $_ properly, nor # will passing argument to subroutines work (via @_). In fact, # for reasons I don't completely understand, we need to share # the whole glob *_ rather than $_ and @_ separately, otherwise # @_ in non default packages within the compartment don't work. $obj->share_from('main', $default_share); Opcode::_safe_pkg_prep($obj->{Root}) if($Opcode::VERSION > 1.04); return $obj; } sub DESTROY { my $obj = shift; $obj->erase('DESTROY') if $obj->{Erase}; } sub erase { my ($obj, $action) = @_; my $pkg = $obj->root(); my ($stem, $leaf); no strict 'refs'; $pkg = "main::$pkg\::"; # expand to full symbol table name ($stem, $leaf) = $pkg =~ m/(.*::)(\w+::)$/; # The 'my $foo' is needed! Without it you get an # 'Attempt to free unreferenced scalar' warning! my $stem_symtab = *{$stem}{HASH}; #warn "erase($pkg) stem=$stem, leaf=$leaf"; #warn " stem_symtab hash ".scalar(%$stem_symtab)."\n"; # ", join(', ', %$stem_symtab),"\n"; # delete $stem_symtab->{$leaf}; my $leaf_glob = $stem_symtab->{$leaf}; my $leaf_symtab = *{$leaf_glob}{HASH}; # warn " leaf_symtab ", join(', ', %$leaf_symtab),"\n"; %$leaf_symtab = (); #delete $leaf_symtab->{'__ANON__'}; #delete $leaf_symtab->{'foo'}; #delete $leaf_symtab->{'main::'}; # my $foo = undef ${"$stem\::"}{"$leaf\::"}; if ($action and $action eq 'DESTROY') { delete $stem_symtab->{$leaf}; } else { $obj->share_from('main', $default_share); } 1; } sub reinit { my $obj= shift; $obj->erase; $obj->share_redo; } sub root { my $obj = shift; croak("Safe root method now read-only") if @_; return $obj->{Root}; } sub mask { my $obj = shift; return $obj->{Mask} unless @_; $obj->deny_only(@_); } # v1 compatibility methods sub trap { shift->deny(@_) } sub untrap { shift->permit(@_) } sub deny { my $obj = shift; $obj->{Mask} |= opset(@_); } sub deny_only { my $obj = shift; $obj->{Mask} = opset(@_); } sub permit { my $obj = shift; # XXX needs testing $obj->{Mask} &= invert_opset opset(@_); } sub permit_only { my $obj = shift; $obj->{Mask} = invert_opset opset(@_); } sub dump_mask { my $obj = shift; print opset_to_hex($obj->{Mask}),"\n"; } sub share { my($obj, @vars) = @_; $obj->share_from(scalar(caller), \@vars); } sub share_from { my $obj = shift; my $pkg = shift; my $vars = shift; my $no_record = shift || 0; my $root = $obj->root(); croak("vars not an array ref") unless ref $vars eq 'ARRAY'; no strict 'refs'; # Check that 'from' package actually exists croak("Package \"$pkg\" does not exist") unless keys %{"$pkg\::"}; my $arg; foreach $arg (@$vars) { # catch some $safe->share($var) errors: my ($var, $type); $type = $1 if ($var = $arg) =~ s/^(\W)//; # warn "share_from $pkg $type $var"; for (1..2) { # assign twice to avoid any 'used once' warnings *{$root."::$var"} = (!$type) ? \&{$pkg."::$var"} : ($type eq '&') ? \&{$pkg."::$var"} : ($type eq '$') ? \${$pkg."::$var"} : ($type eq '@') ? \@{$pkg."::$var"} : ($type eq '%') ? \%{$pkg."::$var"} : ($type eq '*') ? *{$pkg."::$var"} : croak(qq(Can't share "$type$var" of unknown type)); } } $obj->share_record($pkg, $vars) unless $no_record or !$vars; } sub share_record { my $obj = shift; my $pkg = shift; my $vars = shift; my $shares = \%{$obj->{Shares} ||= {}}; # Record shares using keys of $obj->{Shares}. See reinit. @{$shares}{@$vars} = ($pkg) x @$vars if @$vars; } sub share_redo { my $obj = shift; my $shares = \%{$obj->{Shares} ||= {}}; my($var, $pkg); while(($var, $pkg) = each %$shares) { # warn "share_redo $pkg\:: $var"; $obj->share_from($pkg, [ $var ], 1); } } sub share_forget { delete shift->{Shares}; } sub varglob { my ($obj, $var) = @_; no strict 'refs'; return *{$obj->root()."::$var"}; } sub _clean_stash { my ($root, $saved_refs) = @_; $saved_refs ||= []; no strict 'refs'; foreach my $hook (qw(DESTROY AUTOLOAD), grep /^\(/, keys %$root) { push @$saved_refs, \*{$root.$hook}; delete ${$root}{$hook}; } for (grep /::$/, keys %$root) { next if \%{$root.$_} eq \%$root; _clean_stash($root.$_, $saved_refs); } } sub reval { my ($obj, $expr, $strict) = @_; die "Bad Safe object" unless $obj->isa('Safe'); my $root = $obj->{Root}; my $evalsub = lexless_anon_sub($root, $strict, $expr); # propagate context my $sg = sub_generation(); my @subret; if (defined wantarray) { @subret = (wantarray) ? Opcode::_safe_call_sv($root, $obj->{Mask}, $evalsub) : scalar Opcode::_safe_call_sv($root, $obj->{Mask}, $evalsub); } else { Opcode::_safe_call_sv($root, $obj->{Mask}, $evalsub); } _clean_stash($root.'::') if $sg != sub_generation(); $obj->wrap_code_refs_within(@subret); return (wantarray) ? @subret : $subret[0]; } my %OID; sub wrap_code_refs_within { my $obj = shift; %OID = (); $obj->_find_code_refs('wrap_code_ref', @_); } sub _find_code_refs { my $obj = shift; my $visitor = shift; for my $item (@_) { my $reftype = $item && reftype $item or next; # skip references already seen next if ++$OID{refaddr $item} > 1; if ($reftype eq 'ARRAY') { $obj->_find_code_refs($visitor, @$item); } elsif ($reftype eq 'HASH') { $obj->_find_code_refs($visitor, values %$item); } # XXX GLOBs? elsif ($reftype eq 'CODE') { $item = $obj->$visitor($item); } } } sub wrap_code_ref { my ($obj, $sub) = @_; die "Bad safe object" unless $obj->isa('Safe'); # wrap code ref $sub with _safe_call_sv so that, when called, the # execution will happen with the compartment fully 'in effect'. croak "Not a CODE reference" if reftype $sub ne 'CODE'; my $ret = sub { my @args = @_; # lexical to close over my $sub_with_args = sub { $sub->(@args) }; my @subret; my $error; do { local $@; # needed due to perl_call_sv(sv, G_EVAL|G_KEEPERR) my $sg = sub_generation(); @subret = (wantarray) ? Opcode::_safe_call_sv($obj->{Root}, $obj->{Mask}, $sub_with_args) : scalar Opcode::_safe_call_sv($obj->{Root}, $obj->{Mask}, $sub_with_args); $error = $@; _clean_stash($obj->{Root}.'::') if $sg != sub_generation(); }; if ($error) { # rethrow exception $error =~ s/\t\(in cleanup\) //; # prefix added by G_KEEPERR die $error; } return (wantarray) ? @subret : $subret[0]; }; return $ret; } sub rdo { my ($obj, $file) = @_; die "Bad Safe object" unless $obj->isa('Safe'); my $root = $obj->{Root}; my $sg = sub_generation(); my $evalsub = eval sprintf('package %s; sub { @_ = (); do $file }', $root); my @subret = (wantarray) ? Opcode::_safe_call_sv($root, $obj->{Mask}, $evalsub) : scalar Opcode::_safe_call_sv($root, $obj->{Mask}, $evalsub); _clean_stash($root.'::') if $sg != sub_generation(); $obj->wrap_code_refs_within(@subret); return (wantarray) ? @subret : $subret[0]; } 1; __END__ package Env; our $VERSION = '1.04'; sub import { my ($callpack) = caller(0); my $pack = shift; my @vars = grep /^[\$\@]?[A-Za-z_]\w*$/, (@_ ? @_ : keys(%ENV)); return unless @vars; @vars = map { m/^[\$\@]/ ? $_ : '$'.$_ } @vars; eval "package $callpack; use vars qw(" . join(' ', @vars) . ")"; die $@ if $@; foreach (@vars) { my ($type, $name) = m/^([\$\@])(.*)$/; if ($type eq '$') { tie ${"${callpack}::$name"}, Env, $name; } else { if ($^O eq 'VMS') { tie @{"${callpack}::$name"}, Env::Array::VMS, $name; } else { tie @{"${callpack}::$name"}, Env::Array, $name; } } } } sub TIESCALAR { bless \($_[1]); } sub FETCH { my ($self) = @_; $ENV{$$self}; } sub STORE { my ($self, $value) = @_; if (defined($value)) { $ENV{$$self} = $value; } else { delete $ENV{$$self}; } } ###################################################################### package Env::Array; use Config; use Tie::Array; @ISA = qw(Tie::Array); my $sep = $Config::Config{path_sep}; sub TIEARRAY { bless \($_[1]); } sub FETCHSIZE { my ($self) = @_; return 1 + scalar(() = $ENV{$$self} =~ /\Q$sep\E/g); } sub STORESIZE { my ($self, $size) = @_; my @temp = split($sep, $ENV{$$self}); $#temp = $size - 1; $ENV{$$self} = join($sep, @temp); } sub CLEAR { my ($self) = @_; $ENV{$$self} = ''; } sub FETCH { my ($self, $index) = @_; return (split($sep, $ENV{$$self}))[$index]; } sub STORE { my ($self, $index, $value) = @_; my @temp = split($sep, $ENV{$$self}); $temp[$index] = $value; $ENV{$$self} = join($sep, @temp); return $value; } sub EXISTS { my ($self, $index) = @_; return $index < $self->FETCHSIZE; } sub DELETE { my ($self, $index) = @_; my @temp = split($sep, $ENV{$$self}); my $value = splice(@temp, $index, 1, ()); $ENV{$$self} = join($sep, @temp); return $value; } sub PUSH { my $self = shift; my @temp = split($sep, $ENV{$$self}); push @temp, @_; $ENV{$$self} = join($sep, @temp); return scalar(@temp); } sub POP { my ($self) = @_; my @temp = split($sep, $ENV{$$self}); my $result = pop @temp; $ENV{$$self} = join($sep, @temp); return $result; } sub UNSHIFT { my $self = shift; my @temp = split($sep, $ENV{$$self}); my $result = unshift @temp, @_; $ENV{$$self} = join($sep, @temp); return $result; } sub SHIFT { my ($self) = @_; my @temp = split($sep, $ENV{$$self}); my $result = shift @temp; $ENV{$$self} = join($sep, @temp); return $result; } sub SPLICE { my $self = shift; my $offset = shift; my $length = shift; my @temp = split($sep, $ENV{$$self}); if (wantarray) { my @result = splice @temp, $offset, $length, @_; $ENV{$$self} = join($sep, @temp); return @result; } else { my $result = scalar splice @temp, $offset, $length, @_; $ENV{$$self} = join($sep, @temp); return $result; } } ###################################################################### package Env::Array::VMS; use Tie::Array; @ISA = qw(Tie::Array); sub TIEARRAY { bless \($_[1]); } sub FETCHSIZE { my ($self) = @_; my $i = 0; while ($i < 127 and defined $ENV{$$self . ';' . $i}) { $i++; }; return $i; } sub FETCH { my ($self, $index) = @_; return $ENV{$$self . ';' . $index}; } sub EXISTS { my ($self, $index) = @_; return $index < $self->FETCHSIZE; } sub DELETE { } 1; # FindBin.pm # # Copyright (c) 1995 Graham Barr & Nick Ing-Simmons. All rights reserved. # This program is free software; you can redistribute it and/or modify it # under the same terms as Perl itself. package FindBin; use Carp; require 5.000; require Exporter; use Cwd qw(getcwd cwd abs_path); use File::Basename; use File::Spec; @EXPORT_OK = qw($Bin $Script $RealBin $RealScript $Dir $RealDir); %EXPORT_TAGS = (ALL => [qw($Bin $Script $RealBin $RealScript $Dir $RealDir)]); @ISA = qw(Exporter); $VERSION = "1.51"; # needed for VMS-specific filename translation if( $^O eq 'VMS' ) { require VMS::Filespec; VMS::Filespec->import; } sub cwd2 { my $cwd = getcwd(); # getcwd might fail if it hasn't access to the current directory. # try harder. defined $cwd or $cwd = cwd(); $cwd; } sub init { *Dir = \$Bin; *RealDir = \$RealBin; if($0 eq '-e' || $0 eq '-') { # perl invoked with -e or script is on C $Script = $RealScript = $0; $Bin = $RealBin = cwd2(); $Bin = VMS::Filespec::unixify($Bin) if $^O eq 'VMS'; } else { my $script = $0; if ($^O eq 'VMS') { ($Bin,$Script) = VMS::Filespec::rmsexpand($0) =~ /(.*[\]>\/]+)(.*)/s; # C isn't going to work, so unixify first ($Bin = VMS::Filespec::unixify($Bin)) =~ s/\/\z//; ($RealBin,$RealScript) = ($Bin,$Script); } else { croak("Cannot find current script '$0'") unless(-f $script); # Ensure $script contains the complete path in case we C $script = File::Spec->catfile(cwd2(), $script) unless File::Spec->file_name_is_absolute($script); ($Script,$Bin) = fileparse($script); # Resolve $script if it is a link while(1) { my $linktext = readlink($script); ($RealScript,$RealBin) = fileparse($script); last unless defined $linktext; $script = (File::Spec->file_name_is_absolute($linktext)) ? $linktext : File::Spec->catfile($RealBin, $linktext); } # Get absolute paths to directories if ($Bin) { my $BinOld = $Bin; $Bin = abs_path($Bin); defined $Bin or $Bin = File::Spec->canonpath($BinOld); } $RealBin = abs_path($RealBin) if($RealBin); } } } BEGIN { init } *again = \&init; 1; # Keep require happy package Symbol; BEGIN { require 5.005; } require Exporter; @ISA = qw(Exporter); @EXPORT = qw(gensym ungensym qualify qualify_to_ref); @EXPORT_OK = qw(delete_package geniosym); $VERSION = '1.08'; my $genpkg = "Symbol::"; my $genseq = 0; my %global = map {$_ => 1} qw(ARGV ARGVOUT ENV INC SIG STDERR STDIN STDOUT); # # Note that we never _copy_ the glob; we just make a ref to it. # If we did copy it, then SVf_FAKE would be set on the copy, and # glob-specific behaviors (e.g. C<*$ref = \&func>) wouldn't work. # sub gensym () { my $name = "GEN" . $genseq++; my $ref = \*{$genpkg . $name}; delete $$genpkg{$name}; $ref; } sub geniosym () { my $sym = gensym(); # force the IO slot to be filled select(select $sym); *$sym{IO}; } sub ungensym ($) {} sub qualify ($;$) { my ($name) = @_; if (!ref($name) && index($name, '::') == -1 && index($name, "'") == -1) { my $pkg; # Global names: special character, "^xyz", or other. if ($name =~ /^(([^a-z])|(\^[a-z_]+))\z/i || $global{$name}) { # RGS 2001-11-05 : translate leading ^X to control-char $name =~ s/^\^([a-z_])/'qq(\c'.$1.')'/eei; $pkg = "main"; } else { $pkg = (@_ > 1) ? $_[1] : caller; } $name = $pkg . "::" . $name; } $name; } sub qualify_to_ref ($;$) { return \*{ qualify $_[0], @_ > 1 ? $_[1] : caller }; } # # of Safe.pm lineage # sub delete_package ($) { my $pkg = shift; # expand to full symbol table name if needed unless ($pkg =~ /^main::.*::$/) { $pkg = "main$pkg" if $pkg =~ /^::/; $pkg = "main::$pkg" unless $pkg =~ /^main::/; $pkg .= '::' unless $pkg =~ /::$/; } my($stem, $leaf) = $pkg =~ m/(.*::)(\w+::)$/; my $stem_symtab = *{$stem}{HASH}; return unless defined $stem_symtab and exists $stem_symtab->{$leaf}; # free all the symbols in the package my $leaf_symtab = *{$stem_symtab->{$leaf}}{HASH}; foreach my $name (keys %$leaf_symtab) { undef *{$pkg . $name}; } # delete the symbol table %$leaf_symtab = (); delete $stem_symtab->{$leaf}; } 1; package blib; use Cwd; use File::Spec; our $VERSION = '1.07'; our $Verbose = 0; sub import { my $package = shift; my $dir; if ($^O eq "MSWin32" && -f "Win32.xs") { # We don't use getcwd() on Windows because it will internally # call Win32::GetCwd(), which will get the Win32 module loaded. # That means that it would not be possible to run `make test` # for the Win32 module because blib.pm would always load the # installed version before @INC gets updated with the blib path. chomp($dir = `cd`); } else { $dir = getcwd; } if ($^O eq 'VMS') { ($dir = VMS::Filespec::unixify($dir)) =~ s-/\z--; } if (@_) { $dir = shift; $dir =~ s/blib\z//; $dir =~ s,/+\z,,; $dir = File::Spec->curdir unless ($dir); die "$dir is not a directory\n" unless (-d $dir); } # detaint: if the user asked for blib, s/he presumably knew # what s/he wanted $dir = $1 if $dir =~ /^(.*)$/; my $i = 5; my($blib, $blib_lib, $blib_arch); while ($i--) { $blib = File::Spec->catdir($dir, "blib"); $blib_lib = File::Spec->catdir($blib, "lib"); $blib_arch = File::Spec->catdir($blib, "arch"); if (-d $blib && -d $blib_arch && -d $blib_lib) { unshift(@INC,$blib_arch,$blib_lib); warn "Using $blib\n" if $Verbose; return; } $dir = File::Spec->catdir($dir, File::Spec->updir); } die "Cannot find blib even in $dir\n"; } 1; package autouse; #use strict; # debugging only use 5.006; # use warnings $autouse::VERSION = '1.11'; $autouse::DEBUG ||= 0; sub vet_import ($); sub croak { require Carp; Carp::croak(@_); } sub import { my $class = @_ ? shift : 'autouse'; croak "usage: use $class MODULE [,SUBS...]" unless @_; my $module = shift; (my $pm = $module) =~ s{::}{/}g; $pm .= '.pm'; if (exists $INC{$pm}) { vet_import $module; local $Exporter::ExportLevel = $Exporter::ExportLevel + 1; # $Exporter::Verbose = 1; return $module->import(map { (my $f = $_) =~ s/\(.*?\)$//; $f } @_); } # It is not loaded: need to do real work. my $callpkg = caller(0); print "autouse called from $callpkg\n" if $autouse::DEBUG; my $index; for my $f (@_) { my $proto; $proto = $1 if (my $func = $f) =~ s/\((.*)\)$//; my $closure_import_func = $func; # Full name my $closure_func = $func; # Name inside package my $index = rindex($func, '::'); if ($index == -1) { $closure_import_func = "${callpkg}::$func"; } else { $closure_func = substr $func, $index + 2; croak "autouse into different package attempted" unless substr($func, 0, $index) eq $module; } my $load_sub = sub { unless ($INC{$pm}) { require $pm; vet_import $module; } no warnings qw(redefine prototype); *$closure_import_func = \&{"${module}::$closure_func"}; print "autousing $module; " ."imported $closure_func as $closure_import_func\n" if $autouse::DEBUG; goto &$closure_import_func; }; if (defined $proto) { *$closure_import_func = eval "sub ($proto) { goto &\$load_sub }" || die; } else { *$closure_import_func = $load_sub; } } } sub vet_import ($) { my $module = shift; if (my $import = $module->can('import')) { croak "autoused module $module has unique import() method" unless defined(&Exporter::import) && ($import == \&Exporter::import || $import == \&UNIVERSAL::import) } } 1; __END__ # -*- mode: perl; perl-indent-level: 2; -*- # Memoize.pm # # Transparent memoization of idempotent functions # # Copyright 1998, 1999, 2000, 2001, 2012 M. J. Dominus. # You may copy and distribute this program under the # same terms as Perl itself. If in doubt, # write to mjd-perl-memoize+@plover.com for a license. package Memoize; $VERSION = '1.03_01'; # Compile-time constants sub SCALAR () { 0 } sub LIST () { 1 } # # Usage memoize(functionname/ref, # { NORMALIZER => coderef, INSTALL => name, # LIST_CACHE => descriptor, SCALAR_CACHE => descriptor } # use Carp; use Exporter; use vars qw($DEBUG); use Config; # Dammit. @ISA = qw(Exporter); @EXPORT = qw(memoize); @EXPORT_OK = qw(unmemoize flush_cache); use strict; my %memotable; my %revmemotable; my @CONTEXT_TAGS = qw(MERGE TIE MEMORY FAULT HASH); my %IS_CACHE_TAG = map {($_ => 1)} @CONTEXT_TAGS; # Raise an error if the user tries to specify one of thesepackage as a # tie for LIST_CACHE my %scalar_only = map {($_ => 1)} qw(DB_File GDBM_File SDBM_File ODBM_File NDBM_File); sub memoize { my $fn = shift; my %options = @_; my $options = \%options; unless (defined($fn) && (ref $fn eq 'CODE' || ref $fn eq '')) { croak "Usage: memoize 'functionname'|coderef {OPTIONS}"; } my $uppack = caller; # TCL me Elmo! my $cref; # Code reference to original function my $name = (ref $fn ? undef : $fn); # Convert function names to code references $cref = &_make_cref($fn, $uppack); # Locate function prototype, if any my $proto = prototype $cref; if (defined $proto) { $proto = "($proto)" } else { $proto = "" } # I would like to get rid of the eval, but there seems not to be any # other way to set the prototype properly. The switch here for # 'usethreads' works around a bug in threadperl having to do with # magic goto. It would be better to fix the bug and use the magic # goto version everywhere. my $wrapper = $Config{usethreads} ? eval "sub $proto { &_memoizer(\$cref, \@_); }" : eval "sub $proto { unshift \@_, \$cref; goto &_memoizer; }"; my $normalizer = $options{NORMALIZER}; if (defined $normalizer && ! ref $normalizer) { $normalizer = _make_cref($normalizer, $uppack); } my $install_name; if (defined $options->{INSTALL}) { # INSTALL => name $install_name = $options->{INSTALL}; } elsif (! exists $options->{INSTALL}) { # No INSTALL option provided; use original name if possible $install_name = $name; } else { # INSTALL => undef means don't install } if (defined $install_name) { $install_name = $uppack . '::' . $install_name unless $install_name =~ /::/; no strict; local($^W) = 0; # ``Subroutine $install_name redefined at ...'' *{$install_name} = $wrapper; # Install memoized version } $revmemotable{$wrapper} = "" . $cref; # Turn code ref into hash key # These will be the caches my %caches; for my $context (qw(SCALAR LIST)) { # suppress subsequent 'uninitialized value' warnings $options{"${context}_CACHE"} ||= ''; my $cache_opt = $options{"${context}_CACHE"}; my @cache_opt_args; if (ref $cache_opt) { @cache_opt_args = @$cache_opt; $cache_opt = shift @cache_opt_args; } if ($cache_opt eq 'FAULT') { # no cache $caches{$context} = undef; } elsif ($cache_opt eq 'HASH') { # user-supplied hash my $cache = $cache_opt_args[0]; my $package = ref(tied %$cache); if ($context eq 'LIST' && $scalar_only{$package}) { croak("You can't use $package for LIST_CACHE because it can only store scalars"); } $caches{$context} = $cache; } elsif ($cache_opt eq '' || $IS_CACHE_TAG{$cache_opt}) { # default is that we make up an in-memory hash $caches{$context} = {}; # (this might get tied later, or MERGEd away) } else { croak "Unrecognized option to `${context}_CACHE': `$cache_opt' should be one of (@CONTEXT_TAGS); aborting"; } } # Perhaps I should check here that you didn't supply *both* merge # options. But if you did, it does do something reasonable: They # both get merged to the same in-memory hash. if ($options{SCALAR_CACHE} eq 'MERGE' || $options{LIST_CACHE} eq 'MERGE') { $options{MERGED} = 1; $caches{SCALAR} = $caches{LIST}; } # Now deal with the TIE options { my $context; foreach $context (qw(SCALAR LIST)) { # If the relevant option wasn't `TIE', this call does nothing. _my_tie($context, $caches{$context}, $options); # Croaks on failure } } # We should put some more stuff in here eventually. # We've been saying that for serveral versions now. # And you know what? More stuff keeps going in! $memotable{$cref} = { O => $options, # Short keys here for things we need to access frequently N => $normalizer, U => $cref, MEMOIZED => $wrapper, PACKAGE => $uppack, NAME => $install_name, S => $caches{SCALAR}, L => $caches{LIST}, }; $wrapper # Return just memoized version } # This function tries to load a tied hash class and tie the hash to it. sub _my_tie { my ($context, $hash, $options) = @_; my $fullopt = $options->{"${context}_CACHE"}; # We already checked to make sure that this works. my $shortopt = (ref $fullopt) ? $fullopt->[0] : $fullopt; return unless defined $shortopt && $shortopt eq 'TIE'; carp("TIE option to memoize() is deprecated; use HASH instead") if $^W; my @args = ref $fullopt ? @$fullopt : (); shift @args; my $module = shift @args; if ($context eq 'LIST' && $scalar_only{$module}) { croak("You can't use $module for LIST_CACHE because it can only store scalars"); } my $modulefile = $module . '.pm'; $modulefile =~ s{::}{/}g; eval { local @INC = @INC; pop @INC if $INC[-1] eq '.'; require $modulefile }; if ($@) { croak "Memoize: Couldn't load hash tie module `$module': $@; aborting"; } my $rc = (tie %$hash => $module, @args); unless ($rc) { croak "Memoize: Couldn't tie hash to `$module': $!; aborting"; } 1; } sub flush_cache { my $func = _make_cref($_[0], scalar caller); my $info = $memotable{$revmemotable{$func}}; die "$func not memoized" unless defined $info; for my $context (qw(S L)) { my $cache = $info->{$context}; if (tied %$cache && ! (tied %$cache)->can('CLEAR')) { my $funcname = defined($info->{NAME}) ? "function $info->{NAME}" : "anonymous function $func"; my $context = {S => 'scalar', L => 'list'}->{$context}; croak "Tied cache hash for $context-context $funcname does not support flushing"; } else { %$cache = (); } } } # This is the function that manages the memo tables. sub _memoizer { my $orig = shift; # stringized version of ref to original func. my $info = $memotable{$orig}; my $normalizer = $info->{N}; my $argstr; my $context = (wantarray() ? LIST : SCALAR); if (defined $normalizer) { no strict; if ($context == SCALAR) { $argstr = &{$normalizer}(@_); } elsif ($context == LIST) { ($argstr) = &{$normalizer}(@_); } else { croak "Internal error \#41; context was neither LIST nor SCALAR\n"; } } else { # Default normalizer local $^W = 0; $argstr = join chr(28),@_; } if ($context == SCALAR) { my $cache = $info->{S}; _crap_out($info->{NAME}, 'scalar') unless $cache; if (exists $cache->{$argstr}) { return $info->{O}{MERGED} ? $cache->{$argstr}[0] : $cache->{$argstr}; } else { my $val = &{$info->{U}}(@_); # Scalars are considered to be lists; store appropriately if ($info->{O}{MERGED}) { $cache->{$argstr} = [$val]; } else { $cache->{$argstr} = $val; } $val; } } elsif ($context == LIST) { my $cache = $info->{L}; _crap_out($info->{NAME}, 'list') unless $cache; if (exists $cache->{$argstr}) { return @{$cache->{$argstr}}; } else { my @q = &{$info->{U}}(@_); $cache->{$argstr} = \@q; @q; } } else { croak "Internal error \#42; context was neither LIST nor SCALAR\n"; } } sub unmemoize { my $f = shift; my $uppack = caller; my $cref = _make_cref($f, $uppack); unless (exists $revmemotable{$cref}) { croak "Could not unmemoize function `$f', because it was not memoized to begin with"; } my $tabent = $memotable{$revmemotable{$cref}}; unless (defined $tabent) { croak "Could not figure out how to unmemoize function `$f'"; } my $name = $tabent->{NAME}; if (defined $name) { no strict; local($^W) = 0; # ``Subroutine $install_name redefined at ...'' *{$name} = $tabent->{U}; # Replace with original function } undef $memotable{$revmemotable{$cref}}; undef $revmemotable{$cref}; # This removes the last reference to the (possibly tied) memo tables # my ($old_function, $memotabs) = @{$tabent}{'U','S','L'}; # undef $tabent; # # Untie the memo tables if they were tied. # my $i; # for $i (0,1) { # if (tied %{$memotabs->[$i]}) { # warn "Untying hash #$i\n"; # untie %{$memotabs->[$i]}; # } # } $tabent->{U}; } sub _make_cref { my $fn = shift; my $uppack = shift; my $cref; my $name; if (ref $fn eq 'CODE') { $cref = $fn; } elsif (! ref $fn) { if ($fn =~ /::/) { $name = $fn; } else { $name = $uppack . '::' . $fn; } no strict; if (defined $name and !defined(&$name)) { croak "Cannot operate on nonexistent function `$fn'"; } # $cref = \&$name; $cref = *{$name}{CODE}; } else { my $parent = (caller(1))[3]; # Function that called _make_cref croak "Usage: argument 1 to `$parent' must be a function name or reference.\n"; } $DEBUG and warn "${name}($fn) => $cref in _make_cref\n"; $cref; } sub _crap_out { my ($funcname, $context) = @_; if (defined $funcname) { croak "Function `$funcname' called in forbidden $context context; faulting"; } else { croak "Anonymous function called in forbidden $context context; faulting"; } } 1; package charnames; use strict; use warnings; our $VERSION = '1.45'; use unicore::Name; # mktables-generated algorithmically-defined names use _charnames (); # The submodule for this where most of the work gets done use bytes (); # for $bytes::hint_bits use re "/aa"; # Everything in here should be ASCII # Translate between Unicode character names and their code points. # This is a wrapper around the submodule C<_charnames>. This design allows # C<_charnames> to be autoloaded to enable use of \N{...}, but requires this # module to be explicitly requested for the functions API. $Carp::Internal{ (__PACKAGE__) } = 1; sub import { shift; ## ignore class name _charnames->import(@_); } # Cache of already looked-up values. This is set to only contain # official values, and user aliases can't override them, so scoping is # not an issue. my %viacode; sub viacode { return _charnames::viacode(@_); } sub vianame { if (@_ != 1) { _charnames::carp "charnames::vianame() expects one name argument"; return () } # Looks up the character name and returns its ordinal if # found, undef otherwise. my $arg = shift; if ($arg =~ /^U\+([0-9a-fA-F]+)$/) { # khw claims that this is poor interface design. The function should # return either a an ord or a chr for all inputs; not be bipolar. But # can't change it because of backward compatibility. New code can use # string_vianame() instead. my $ord = CORE::hex $1; return pack("U", $ord) if $ord <= 255 || ! ((caller 0)[8] & $bytes::hint_bits); _charnames::carp _charnames::not_legal_use_bytes_msg($arg, chr $ord); return; } # The first 1 arg means wants an ord returned; the second that we are in # runtime, and this is the first level routine called from the user return _charnames::lookup_name($arg, 1, 1); } # vianame sub string_vianame { # Looks up the character name and returns its string representation if # found, undef otherwise. if (@_ != 1) { _charnames::carp "charnames::string_vianame() expects one name argument"; return; } my $arg = shift; if ($arg =~ /^U\+([0-9a-fA-F]+)$/) { my $ord = CORE::hex $1; return pack("U", $ord) if $ord <= 255 || ! ((caller 0)[8] & $bytes::hint_bits); _charnames::carp _charnames::not_legal_use_bytes_msg($arg, chr $ord); return; } # The 0 arg means wants a string returned; the 1 arg means that we are in # runtime, and this is the first level routine called from the user return _charnames::lookup_name($arg, 0, 1); } # string_vianame 1; __END__ # ex: set ts=8 sts=2 sw=2 et: use 5.008; package fields; require 5.005; use strict; no strict 'refs'; unless( eval q{require warnings::register; warnings::register->import; 1} ) { *warnings::warnif = sub { require Carp; Carp::carp(@_); } } our %attr; our $VERSION = '2.24'; $VERSION =~ tr/_//d; # constant.pm is slow sub PUBLIC () { 2**0 } sub PRIVATE () { 2**1 } sub INHERITED () { 2**2 } sub PROTECTED () { 2**3 } # The %attr hash holds the attributes of the currently assigned fields # per class. The hash is indexed by class names and the hash value is # an array reference. The first element in the array is the lowest field # number not belonging to a base class. The remaining elements' indices # are the field numbers. The values are integer bit masks, or undef # in the case of base class private fields (which occupy a slot but are # otherwise irrelevant to the class). sub import { my $class = shift; return unless @_; my $package = caller(0); # avoid possible typo warnings %{"$package\::FIELDS"} = () unless %{"$package\::FIELDS"}; my $fields = \%{"$package\::FIELDS"}; my $fattr = ($attr{$package} ||= [1]); my $next = @$fattr; # Quiet pseudo-hash deprecation warning for uses of fields::new. bless \%{"$package\::FIELDS"}, 'pseudohash'; if ($next > $fattr->[0] and ($fields->{$_[0]} || 0) >= $fattr->[0]) { # There are already fields not belonging to base classes. # Looks like a possible module reload... $next = $fattr->[0]; } foreach my $f (@_) { my $fno = $fields->{$f}; # Allow the module to be reloaded so long as field positions # have not changed. if ($fno and $fno != $next) { require Carp; if ($fno < $fattr->[0]) { if ($] < 5.006001) { warn("Hides field '$f' in base class") if $^W; } else { warnings::warnif("Hides field '$f' in base class") ; } } else { Carp::croak("Field name '$f' already in use"); } } $fields->{$f} = $next; $fattr->[$next] = ($f =~ /^_/) ? PRIVATE : PUBLIC; $next += 1; } if (@$fattr > $next) { # Well, we gave them the benefit of the doubt by guessing the # module was reloaded, but they appear to be declaring fields # in more than one place. We can't be sure (without some extra # bookkeeping) that the rest of the fields will be declared or # have the same positions, so punt. require Carp; Carp::croak ("Reloaded module must declare all fields at once"); } } sub inherit { require base; goto &base::inherit_fields; } sub _dump # sometimes useful for debugging { for my $pkg (sort keys %attr) { print "\n$pkg"; if (@{"$pkg\::ISA"}) { print " (", join(", ", @{"$pkg\::ISA"}), ")"; } print "\n"; my $fields = \%{"$pkg\::FIELDS"}; for my $f (sort {$fields->{$a} <=> $fields->{$b}} keys %$fields) { my $no = $fields->{$f}; print " $no: $f"; my $fattr = $attr{$pkg}[$no]; if (defined $fattr) { my @a; push(@a, "public") if $fattr & PUBLIC; push(@a, "private") if $fattr & PRIVATE; push(@a, "inherited") if $fattr & INHERITED; print "\t(", join(", ", @a), ")"; } print "\n"; } } } if ($] < 5.009) { *new = sub { my $class = shift; $class = ref $class if ref $class; return bless [\%{$class . "::FIELDS"}], $class; } } else { *new = sub { my $class = shift; $class = ref $class if ref $class; require Hash::Util; my $self = bless {}, $class; # The lock_keys() prototype won't work since we require Hash::Util :( &Hash::Util::lock_keys(\%$self, _accessible_keys($class)); return $self; } } sub _accessible_keys { my ($class) = @_; return ( keys %{$class.'::FIELDS'}, map(_accessible_keys($_), @{$class.'::ISA'}), ); } sub phash { die "Pseudo-hashes have been removed from Perl" if $] >= 5.009; my $h; my $v; if (@_) { if (ref $_[0] eq 'ARRAY') { my $a = shift; @$h{@$a} = 1 .. @$a; if (@_) { $v = shift; unless (! @_ and ref $v eq 'ARRAY') { require Carp; Carp::croak ("Expected at most two array refs\n"); } } } else { if (@_ % 2) { require Carp; Carp::croak ("Odd number of elements initializing pseudo-hash\n"); } my $i = 0; @$h{grep ++$i % 2, @_} = 1 .. @_ / 2; $i = 0; $v = [grep $i++ % 2, @_]; } } else { $h = {}; $v = []; } [ $h, @$v ]; } 1; __END__ package Thread; use strict; use warnings; no warnings 'redefine'; our $VERSION = '3.04'; $VERSION = eval $VERSION; BEGIN { use Config; if (! $Config{useithreads}) { die("This Perl not built to support threads\n"); } } use threads 'yield'; use threads::shared; require Exporter; our @ISA = qw(Exporter threads); our @EXPORT = qw(cond_wait cond_broadcast cond_signal); our @EXPORT_OK = qw(async yield); sub async (&;@) { return Thread->new(shift); } sub done { return ! shift->is_running(); } sub eval { die("'eval' not implemented with 'ithreads'\n"); }; sub flags { die("'flags' not implemented with 'ithreads'\n"); }; 1; __END__ package parent; use strict; use vars qw($VERSION); $VERSION = '0.236'; sub import { my $class = shift; my $inheritor = caller(0); if ( @_ and $_[0] eq '-norequire' ) { shift @_; } else { for ( my @filename = @_ ) { s{::|'}{/}g; require "$_.pm"; # dies if the file is not found } } { no strict 'refs'; push @{"$inheritor\::ISA"}, @_; # dies if a loop is detected }; }; 1; __END__ package less; use strict; use warnings; our $VERSION = '0.03'; sub _pack_tags { return join ' ', @_; } sub _unpack_tags { return grep { defined and length } map { split ' ' } grep {defined} @_; } sub stash_name { $_[0] } sub of { my $class = shift @_; # If no one wants the result, don't bother computing it. return unless defined wantarray; my $hinthash = ( caller 0 )[10]; my %tags; @tags{ _unpack_tags( $hinthash->{ $class->stash_name } ) } = (); if (@_) { exists $tags{$_} and return !!1 for @_; return; } else { return keys %tags; } } sub import { my $class = shift @_; my $stash = $class->stash_name; @_ = 'please' if not @_; my %tags; @tags{ _unpack_tags( @_, $^H{ $stash } ) } = (); $^H{$stash} = _pack_tags( keys %tags ); return; } sub unimport { my $class = shift @_; if (@_) { my %tags; @tags{ _unpack_tags( $^H{$class} ) } = (); delete @tags{ _unpack_tags(@_) }; my $new = _pack_tags( keys %tags ); if ( not length $new ) { delete $^H{ $class->stash_name }; } else { $^H{ $class->stash_name } = $new; } } else { delete $^H{ $class->stash_name }; } return; } 1; __END__ package Benchmark; use strict; # evaluate something in a clean lexical environment sub _doeval { no strict; eval shift } # # put any lexicals at file scope AFTER here # use Carp; use Exporter; our(@ISA, @EXPORT, @EXPORT_OK, %EXPORT_TAGS, $VERSION); @ISA=qw(Exporter); @EXPORT=qw(timeit timethis timethese timediff timestr); @EXPORT_OK=qw(timesum cmpthese countit clearcache clearallcache disablecache enablecache); %EXPORT_TAGS=( all => [ @EXPORT, @EXPORT_OK ] ) ; $VERSION = 1.22; # --- ':hireswallclock' special handling my $hirestime; sub mytime () { time } init(); sub BEGIN { if (eval 'require Time::HiRes') { import Time::HiRes qw(time); $hirestime = \&Time::HiRes::time; } } sub import { my $class = shift; if (grep { $_ eq ":hireswallclock" } @_) { @_ = grep { $_ ne ":hireswallclock" } @_; local $^W=0; *mytime = $hirestime if defined $hirestime; } Benchmark->export_to_level(1, $class, @_); } our($Debug, $Min_Count, $Min_CPU, $Default_Format, $Default_Style, %_Usage, %Cache, $Do_Cache); sub init { $Debug = 0; $Min_Count = 4; $Min_CPU = 0.4; $Default_Format = '5.2f'; $Default_Style = 'auto'; # The cache can cause a slight loss of sys time accuracy. If a # user does many tests (>10) with *very* large counts (>10000) # or works on a very slow machine the cache may be useful. disablecache(); clearallcache(); } sub debug { $Debug = ($_[1] != 0); } sub usage { my $calling_sub = (caller(1))[3]; $calling_sub =~ s/^Benchmark:://; return $_Usage{$calling_sub} || ''; } # The cache needs two branches: 's' for strings and 'c' for code. The # empty loop is different in these two cases. $_Usage{clearcache} = <<'USAGE'; usage: clearcache($count); USAGE sub clearcache { die usage unless @_ == 1; delete $Cache{"$_[0]c"}; delete $Cache{"$_[0]s"}; } $_Usage{clearallcache} = <<'USAGE'; usage: clearallcache(); USAGE sub clearallcache { die usage if @_; %Cache = (); } $_Usage{enablecache} = <<'USAGE'; usage: enablecache(); USAGE sub enablecache { die usage if @_; $Do_Cache = 1; } $_Usage{disablecache} = <<'USAGE'; usage: disablecache(); USAGE sub disablecache { die usage if @_; $Do_Cache = 0; } # --- Functions to process the 'time' data type sub new { my @t = (mytime, times, @_ == 2 ? $_[1] : 0); print STDERR "new=@t\n" if $Debug; bless \@t; } sub cpu_p { my($r,$pu,$ps,$cu,$cs) = @{$_[0]}; $pu+$ps ; } sub cpu_c { my($r,$pu,$ps,$cu,$cs) = @{$_[0]}; $cu+$cs ; } sub cpu_a { my($r,$pu,$ps,$cu,$cs) = @{$_[0]}; $pu+$ps+$cu+$cs ; } sub real { my($r,$pu,$ps,$cu,$cs) = @{$_[0]}; $r ; } sub iters { $_[0]->[5] ; } # return the sum of various times: which ones depending on $style sub elapsed { my ($self, $style) = @_; $style = "" unless defined $style; return $self->cpu_c if $style eq 'nop'; return $self->cpu_p if $style eq 'noc'; return $self->cpu_a; } $_Usage{timediff} = <<'USAGE'; usage: $result_diff = timediff($result1, $result2); USAGE sub timediff { my($a, $b) = @_; die usage unless ref $a and ref $b; my @r; for (my $i=0; $i < @$a; ++$i) { push(@r, $a->[$i] - $b->[$i]); } #die "Bad timediff(): ($r[1] + $r[2]) <= 0 (@$a[1,2]|@$b[1,2])\n" # if ($r[1] + $r[2]) < 0; bless \@r; } $_Usage{timesum} = <<'USAGE'; usage: $sum = timesum($result1, $result2); USAGE sub timesum { my($a, $b) = @_; die usage unless ref $a and ref $b; my @r; for (my $i=0; $i < @$a; ++$i) { push(@r, $a->[$i] + $b->[$i]); } bless \@r; } $_Usage{timestr} = <<'USAGE'; usage: $formatted_result = timestr($result1); USAGE sub timestr { my($tr, $style, $f) = @_; die usage unless ref $tr; my @t = @$tr; warn "bad time value (@t)" unless @t==6; my($r, $pu, $ps, $cu, $cs, $n) = @t; my($pt, $ct, $tt) = ($tr->cpu_p, $tr->cpu_c, $tr->cpu_a); $f = $Default_Format unless defined $f; # format a time in the required style, other formats may be added here $style ||= $Default_Style; return '' if $style eq 'none'; $style = ($ct>0) ? 'all' : 'noc' if $style eq 'auto'; my $s = "@t $style"; # default for unknown style my $w = $hirestime ? "%2g" : "%2d"; $s = sprintf("$w wallclock secs (%$f usr %$f sys + %$f cusr %$f csys = %$f CPU)", $r,$pu,$ps,$cu,$cs,$tt) if $style eq 'all'; $s = sprintf("$w wallclock secs (%$f usr + %$f sys = %$f CPU)", $r,$pu,$ps,$pt) if $style eq 'noc'; $s = sprintf("$w wallclock secs (%$f cusr + %$f csys = %$f CPU)", $r,$cu,$cs,$ct) if $style eq 'nop'; my $elapsed = $tr->elapsed($style); $s .= sprintf(" @ %$f/s (n=$n)",$n/($elapsed)) if $n && $elapsed; $s; } sub timedebug { my($msg, $t) = @_; print STDERR "$msg",timestr($t),"\n" if $Debug; } # --- Functions implementing low-level support for timing loops $_Usage{runloop} = <<'USAGE'; usage: runloop($number, [$string | $coderef]) USAGE sub runloop { my($n, $c) = @_; $n+=0; # force numeric now, so garbage won't creep into the eval croak "negative loopcount $n" if $n<0; confess usage unless defined $c; my($t0, $t1, $td); # before, after, difference # find package of caller so we can execute code there my($curpack) = caller(0); my($i, $pack)= 0; while (($pack) = caller(++$i)) { last if $pack ne $curpack; } my ($subcode, $subref); if (ref $c eq 'CODE') { $subcode = "sub { for (1 .. $n) { local \$_; package $pack; &\$c; } }"; $subref = eval $subcode; } else { $subcode = "sub { for (1 .. $n) { local \$_; package $pack; $c;} }"; $subref = _doeval($subcode); } croak "runloop unable to compile '$c': $@\ncode: $subcode\n" if $@; print STDERR "runloop $n '$subcode'\n" if $Debug; # Wait for the user timer to tick. This makes the error range more like # -0.01, +0. If we don't wait, then it's more like -0.01, +0.01. This # may not seem important, but it significantly reduces the chances of # getting a too low initial $n in the initial, 'find the minimum' loop # in &countit. This, in turn, can reduce the number of calls to # &runloop a lot, and thus reduce additive errors. # # Note that its possible for the act of reading the system clock to # burn lots of system CPU while we burn very little user clock in the # busy loop, which can cause the loop to run for a very long wall time. # So gradually ramp up the duration of the loop. See RT #122003 # my $tbase = Benchmark->new(0)->[1]; my $limit = 1; while ( ( $t0 = Benchmark->new(0) )->[1] == $tbase ) { for (my $i=0; $i < $limit; $i++) { my $x = $i / 1.5 } # burn user CPU $limit *= 1.1; } $subref->(); $t1 = Benchmark->new($n); $td = &timediff($t1, $t0); timedebug("runloop:",$td); $td; } $_Usage{timeit} = <<'USAGE'; usage: $result = timeit($count, 'code' ); or $result = timeit($count, sub { code } ); USAGE sub timeit { my($n, $code) = @_; my($wn, $wc, $wd); die usage unless defined $code and (!ref $code or ref $code eq 'CODE'); printf STDERR "timeit $n $code\n" if $Debug; my $cache_key = $n . ( ref( $code ) ? 'c' : 's' ); if ($Do_Cache && exists $Cache{$cache_key} ) { $wn = $Cache{$cache_key}; } else { $wn = &runloop($n, ref( $code ) ? sub { } : '' ); # Can't let our baseline have any iterations, or they get subtracted # out of the result. $wn->[5] = 0; $Cache{$cache_key} = $wn; } $wc = &runloop($n, $code); $wd = timediff($wc, $wn); timedebug("timeit: ",$wc); timedebug(" - ",$wn); timedebug(" = ",$wd); $wd; } my $default_for = 3; my $min_for = 0.1; $_Usage{countit} = <<'USAGE'; usage: $result = countit($time, 'code' ); or $result = countit($time, sub { code } ); USAGE sub countit { my ( $tmax, $code ) = @_; die usage unless @_; if ( not defined $tmax or $tmax == 0 ) { $tmax = $default_for; } elsif ( $tmax < 0 ) { $tmax = -$tmax; } die "countit($tmax, ...): timelimit cannot be less than $min_for.\n" if $tmax < $min_for; my ($n, $tc); # First find the minimum $n that gives a significant timing. my $zeros=0; for ($n = 1; ; $n *= 2 ) { my $t0 = Benchmark->new(0); my $td = timeit($n, $code); my $t1 = Benchmark->new(0); $tc = $td->[1] + $td->[2]; if ( $tc <= 0 and $n > 1024 ) { my $d = timediff($t1, $t0); # note that $d is the total CPU time taken to call timeit(), # while $tc is is difference in CPU secs between the empty run # and the code run. If the code is trivial, its possible # for $d to get large while $tc is still zero (or slightly # negative). Bail out once timeit() starts taking more than a # few seconds without noticeable difference. if ($d->[1] + $d->[2] > 8 || ++$zeros > 16) { die "Timing is consistently zero in estimation loop, cannot benchmark. N=$n\n"; } } else { $zeros = 0; } last if $tc > 0.1; } my $nmin = $n; # Get $n high enough that we can guess the final $n with some accuracy. my $tpra = 0.1 * $tmax; # Target/time practice. while ( $tc < $tpra ) { # The 5% fudge is to keep us from iterating again all # that often (this speeds overall responsiveness when $tmax is big # and we guess a little low). This does not noticeably affect # accuracy since we're not counting these times. $n = int( $tpra * 1.05 * $n / $tc ); # Linear approximation. my $td = timeit($n, $code); my $new_tc = $td->[1] + $td->[2]; # Make sure we are making progress. $tc = $new_tc > 1.2 * $tc ? $new_tc : 1.2 * $tc; } # Now, do the 'for real' timing(s), repeating until we exceed # the max. my $ntot = 0; my $rtot = 0; my $utot = 0.0; my $stot = 0.0; my $cutot = 0.0; my $cstot = 0.0; my $ttot = 0.0; # The 5% fudge is because $n is often a few % low even for routines # with stable times and avoiding extra timeit()s is nice for # accuracy's sake. $n = int( $n * ( 1.05 * $tmax / $tc ) ); $zeros=0; while () { my $td = timeit($n, $code); $ntot += $n; $rtot += $td->[0]; $utot += $td->[1]; $stot += $td->[2]; $cutot += $td->[3]; $cstot += $td->[4]; $ttot = $utot + $stot; last if $ttot >= $tmax; if ( $ttot <= 0 ) { ++$zeros > 16 and die "Timing is consistently zero, cannot benchmark. N=$n\n"; } else { $zeros = 0; } $ttot = 0.01 if $ttot < 0.01; my $r = $tmax / $ttot - 1; # Linear approximation. $n = int( $r * $ntot ); $n = $nmin if $n < $nmin; } return bless [ $rtot, $utot, $stot, $cutot, $cstot, $ntot ]; } # --- Functions implementing high-level time-then-print utilities sub n_to_for { my $n = shift; return $n == 0 ? $default_for : $n < 0 ? -$n : undef; } $_Usage{timethis} = <<'USAGE'; usage: $result = timethis($time, 'code' ); or $result = timethis($time, sub { code } ); USAGE sub timethis{ my($n, $code, $title, $style) = @_; my($t, $forn); die usage unless defined $code and (!ref $code or ref $code eq 'CODE'); if ( $n > 0 ) { croak "non-integer loopcount $n, stopped" if int($n)<$n; $t = timeit($n, $code); $title = "timethis $n" unless defined $title; } else { my $fort = n_to_for( $n ); $t = countit( $fort, $code ); $title = "timethis for $fort" unless defined $title; $forn = $t->[-1]; } local $| = 1; $style = "" unless defined $style; printf("%10s: ", $title) unless $style eq 'none'; print timestr($t, $style, $Default_Format),"\n" unless $style eq 'none'; $n = $forn if defined $forn; if ($t->elapsed($style) < 0) { # due to clock granularity and variable CPU speed and load, # on quick code with a small number of loops, it's possible for # the empty loop to appear to take longer than the real loop # (e.g. 1 tick versus 0 ticks). This leads to a negative elapsed # time. In this case, floor it at zero, to stop bizarre results. print " (warning: too few iterations for a reliable count)\n"; $t->[$_] = 0 for 1..4; } # A conservative warning to spot very silly tests. # Don't assume that your benchmark is ok simply because # you don't get this warning! print " (warning: too few iterations for a reliable count)\n" if $n < $Min_Count || ($t->real < 1 && $n < 1000) || $t->cpu_a < $Min_CPU; $t; } $_Usage{timethese} = <<'USAGE'; usage: timethese($count, { Name1 => 'code1', ... }); or timethese($count, { Name1 => sub { code1 }, ... }); USAGE sub timethese{ my($n, $alt, $style) = @_; die usage unless ref $alt eq 'HASH'; my @names = sort keys %$alt; $style = "" unless defined $style; print "Benchmark: " unless $style eq 'none'; if ( $n > 0 ) { croak "non-integer loopcount $n, stopped" if int($n)<$n; print "timing $n iterations of" unless $style eq 'none'; } else { print "running" unless $style eq 'none'; } print " ", join(', ',@names) unless $style eq 'none'; unless ( $n > 0 ) { my $for = n_to_for( $n ); print ", each" if $n > 1 && $style ne 'none'; print " for at least $for CPU seconds" unless $style eq 'none'; } print "...\n" unless $style eq 'none'; # we could save the results in an array and produce a summary here # sum, min, max, avg etc etc my %results; foreach my $name (@names) { $results{$name} = timethis ($n, $alt -> {$name}, $name, $style); } return \%results; } $_Usage{cmpthese} = <<'USAGE'; usage: cmpthese($count, { Name1 => 'code1', ... }); or cmpthese($count, { Name1 => sub { code1 }, ... }); or cmpthese($result, $style); USAGE sub cmpthese{ my ($results, $style); # $count can be a blessed object. if ( ref $_[0] eq 'HASH' ) { ($results, $style) = @_; } else { my($count, $code) = @_[0,1]; $style = $_[2] if defined $_[2]; die usage unless ref $code eq 'HASH'; $results = timethese($count, $code, ($style || "none")); } $style = "" unless defined $style; # Flatten in to an array of arrays with the name as the first field my @vals = map{ [ $_, @{$results->{$_}} ] } keys %$results; for (@vals) { # recreate the pre-flattened Benchmark object my $tmp_bm = bless [ @{$_}[1..$#$_] ]; my $elapsed = $tmp_bm->elapsed($style); # The epsilon fudge here is to prevent div by 0. Since clock # resolutions are much larger, it's below the noise floor. my $rate = $_->[6]/(($elapsed)+0.000000000000001); $_->[7] = $rate; } # Sort by rate @vals = sort { $a->[7] <=> $b->[7] } @vals; # If more than half of the rates are greater than one... my $display_as_rate = @vals ? ($vals[$#vals>>1]->[7] > 1) : 0; my @rows; my @col_widths; my @top_row = ( '', $display_as_rate ? 'Rate' : 's/iter', map { $_->[0] } @vals ); push @rows, \@top_row; @col_widths = map { length( $_ ) } @top_row; # Build the data rows # We leave the last column in even though it never has any data. Perhaps # it should go away. Also, perhaps a style for a single column of # percentages might be nice. for my $row_val ( @vals ) { my @row; # Column 0 = test name push @row, $row_val->[0]; $col_widths[0] = length( $row_val->[0] ) if length( $row_val->[0] ) > $col_widths[0]; # Column 1 = performance my $row_rate = $row_val->[7]; # We assume that we'll never get a 0 rate. my $rate = $display_as_rate ? $row_rate : 1 / $row_rate; # Only give a few decimal places before switching to sci. notation, # since the results aren't usually that accurate anyway. my $format = $rate >= 100 ? "%0.0f" : $rate >= 10 ? "%0.1f" : $rate >= 1 ? "%0.2f" : $rate >= 0.1 ? "%0.3f" : "%0.2e"; $format .= "/s" if $display_as_rate; my $formatted_rate = sprintf( $format, $rate ); push @row, $formatted_rate; $col_widths[1] = length( $formatted_rate ) if length( $formatted_rate ) > $col_widths[1]; # Columns 2..N = performance ratios my $skip_rest = 0; for ( my $col_num = 0 ; $col_num < @vals ; ++$col_num ) { my $col_val = $vals[$col_num]; my $out; if ( $skip_rest ) { $out = ''; } elsif ( $col_val->[0] eq $row_val->[0] ) { $out = "--"; # $skip_rest = 1; } else { my $col_rate = $col_val->[7]; $out = sprintf( "%.0f%%", 100*$row_rate/$col_rate - 100 ); } push @row, $out; $col_widths[$col_num+2] = length( $out ) if length( $out ) > $col_widths[$col_num+2]; # A little weirdness to set the first column width properly $col_widths[$col_num+2] = length( $col_val->[0] ) if length( $col_val->[0] ) > $col_widths[$col_num+2]; } push @rows, \@row; } return \@rows if $style eq "none"; # Equalize column widths in the chart as much as possible without # exceeding 80 characters. This does not use or affect cols 0 or 1. my @sorted_width_refs = sort { $$a <=> $$b } map { \$_ } @col_widths[2..$#col_widths]; my $max_width = ${$sorted_width_refs[-1]}; my $total = @col_widths - 1 ; for ( @col_widths ) { $total += $_ } STRETCHER: while ( $total < 80 ) { my $min_width = ${$sorted_width_refs[0]}; last if $min_width == $max_width; for ( @sorted_width_refs ) { last if $$_ > $min_width; ++$$_; ++$total; last STRETCHER if $total >= 80; } } # Dump the output my $format = join( ' ', map { "%${_}s" } @col_widths ) . "\n"; substr( $format, 1, 0 ) = '-'; for ( @rows ) { printf $format, @$_; } return \@rows ; } 1; package Carp; { use 5.006; } use strict; use warnings; BEGIN { # Very old versions of warnings.pm load Carp. This can go wrong due # to the circular dependency. If warnings is invoked before Carp, # then warnings starts by loading Carp, then Carp (above) tries to # invoke warnings, and gets nothing because warnings is in the process # of loading and hasn't defined its import method yet. If we were # only turning on warnings ("use warnings" above) this wouldn't be too # bad, because Carp would just gets the state of the -w switch and so # might not get some warnings that it wanted. The real problem is # that we then want to turn off Unicode warnings, but "no warnings # 'utf8'" won't be effective if we're in this circular-dependency # situation. So, if warnings.pm is an affected version, we turn # off all warnings ourselves by directly setting ${^WARNING_BITS}. # On unaffected versions, we turn off just Unicode warnings, via # the proper API. if(!defined($warnings::VERSION) || eval($warnings::VERSION) < 1.06) { ${^WARNING_BITS} = ""; } else { "warnings"->unimport("utf8"); } } sub _fetch_sub { # fetch sub without autovivifying my($pack, $sub) = @_; $pack .= '::'; # only works with top-level packages return unless exists($::{$pack}); for ($::{$pack}) { return unless ref \$_ eq 'GLOB' && *$_{HASH} && exists $$_{$sub}; for ($$_{$sub}) { return ref \$_ eq 'GLOB' ? *$_{CODE} : undef } } } # UTF8_REGEXP_PROBLEM is a compile-time constant indicating whether Carp # must avoid applying a regular expression to an upgraded (is_utf8) # string. There are multiple problems, on different Perl versions, # that require this to be avoided. All versions prior to 5.13.8 will # load utf8_heavy.pl for the swash system, even if the regexp doesn't # use character classes. Perl 5.6 and Perls [5.11.2, 5.13.11) exhibit # specific problems when Carp is being invoked in the aftermath of a # syntax error. BEGIN { if("$]" < 5.013011) { *UTF8_REGEXP_PROBLEM = sub () { 1 }; } else { *UTF8_REGEXP_PROBLEM = sub () { 0 }; } } # is_utf8() is essentially the utf8::is_utf8() function, which indicates # whether a string is represented in the upgraded form (using UTF-8 # internally). As utf8::is_utf8() is only available from Perl 5.8 # onwards, extra effort is required here to make it work on Perl 5.6. BEGIN { if(defined(my $sub = _fetch_sub utf8 => 'is_utf8')) { *is_utf8 = $sub; } else { # black magic for perl 5.6 *is_utf8 = sub { unpack("C", "\xaa".$_[0]) != 170 }; } } # The downgrade() function defined here is to be used for attempts to # downgrade where it is acceptable to fail. It must be called with a # second argument that is a true value. BEGIN { if(defined(my $sub = _fetch_sub utf8 => 'downgrade')) { *downgrade = \&{"utf8::downgrade"}; } else { *downgrade = sub { my $r = ""; my $l = length($_[0]); for(my $i = 0; $i != $l; $i++) { my $o = ord(substr($_[0], $i, 1)); return if $o > 255; $r .= chr($o); } $_[0] = $r; }; } } # is_safe_printable_codepoint() indicates whether a character, specified # by integer codepoint, is OK to output literally in a trace. Generally # this is if it is a printable character in the ancestral character set # (ASCII or EBCDIC). This is used on some Perls in situations where a # regexp can't be used. BEGIN { *is_safe_printable_codepoint = "$]" >= 5.007_003 ? eval(q(sub ($) { my $u = utf8::native_to_unicode($_[0]); $u >= 0x20 && $u <= 0x7e; })) : ord("A") == 65 ? sub ($) { $_[0] >= 0x20 && $_[0] <= 0x7e } : sub ($) { # Early EBCDIC # 3 EBCDIC code pages supported then; all controls but one # are the code points below SPACE. The other one is 0x5F on # POSIX-BC; FF on the other two. # FIXME: there are plenty of unprintable codepoints other # than those that this code and the comment above identifies # as "controls". $_[0] >= ord(" ") && $_[0] <= 0xff && $_[0] != (ord ("^") == 106 ? 0x5f : 0xff); } ; } sub _univ_mod_loaded { return 0 unless exists($::{"UNIVERSAL::"}); for ($::{"UNIVERSAL::"}) { return 0 unless ref \$_ eq "GLOB" && *$_{HASH} && exists $$_{"$_[0]::"}; for ($$_{"$_[0]::"}) { return 0 unless ref \$_ eq "GLOB" && *$_{HASH} && exists $$_{"VERSION"}; for ($$_{"VERSION"}) { return 0 unless ref \$_ eq "GLOB"; return ${*$_{SCALAR}}; } } } } # _maybe_isa() is usually the UNIVERSAL::isa function. We have to avoid # the latter if the UNIVERSAL::isa module has been loaded, to avoid infi- # nite recursion; in that case _maybe_isa simply returns true. my $isa; BEGIN { if (_univ_mod_loaded('isa')) { *_maybe_isa = sub { 1 } } else { # Since we have already done the check, record $isa for use below # when defining _StrVal. *_maybe_isa = $isa = _fetch_sub(UNIVERSAL => "isa"); } } # We need an overload::StrVal or equivalent function, but we must avoid # loading any modules on demand, as Carp is used from __DIE__ handlers and # may be invoked after a syntax error. # We can copy recent implementations of overload::StrVal and use # overloading.pm, which is the fastest implementation, so long as # overloading is available. If it is not available, we use our own pure- # Perl StrVal. We never actually use overload::StrVal, for various rea- # sons described below. # overload versions are as follows: # undef-1.00 (up to perl 5.8.0) uses bless (avoid!) # 1.01-1.17 (perl 5.8.1 to 5.14) uses Scalar::Util # 1.18+ (perl 5.16+) uses overloading # The ancient 'bless' implementation (that inspires our pure-Perl version) # blesses unblessed references and must be avoided. Those using # Scalar::Util use refaddr, possibly the pure-Perl implementation, which # has the same blessing bug, and must be avoided. Also, Scalar::Util is # loaded on demand. Since we avoid the Scalar::Util implementations, we # end up having to implement our own overloading.pm-based version for perl # 5.10.1 to 5.14. Since it also works just as well in more recent ver- # sions, we use it there, too. BEGIN { if (eval { require "overloading.pm" }) { *_StrVal = eval 'sub { no overloading; "$_[0]" }' } else { # Work around the UNIVERSAL::can/isa modules to avoid recursion. # _mycan is either UNIVERSAL::can, or, in the presence of an # override, overload::mycan. *_mycan = _univ_mod_loaded('can') ? do { require "overload.pm"; _fetch_sub overload => 'mycan' } : \&UNIVERSAL::can; # _blessed is either UNIVERAL::isa(...), or, in the presence of an # override, a hideous, but fairly reliable, workaround. *_blessed = $isa ? sub { &$isa($_[0], "UNIVERSAL") } : sub { my $probe = "UNIVERSAL::Carp_probe_" . rand; no strict 'refs'; local *$probe = sub { "unlikely string" }; local $@; local $SIG{__DIE__} = sub{}; (eval { $_[0]->$probe } || '') eq 'unlikely string' }; *_StrVal = sub { my $pack = ref $_[0]; # Perl's overload mechanism uses the presence of a special # "method" named "((" or "()" to signal it is in effect. # This test seeks to see if it has been set up. "((" post- # dates overloading.pm, so we can skip it. return "$_[0]" unless _mycan($pack, "()"); # Even at this point, the invocant may not be blessed, so # check for that. return "$_[0]" if not _blessed($_[0]); bless $_[0], "Carp"; my $str = "$_[0]"; bless $_[0], $pack; $pack . substr $str, index $str, "="; } } } our $VERSION = '1.50'; $VERSION =~ tr/_//d; our $MaxEvalLen = 0; our $Verbose = 0; our $CarpLevel = 0; our $MaxArgLen = 64; # How much of each argument to print. 0 = all. our $MaxArgNums = 8; # How many arguments to print. 0 = all. our $RefArgFormatter = undef; # allow caller to format reference arguments require Exporter; our @ISA = ('Exporter'); our @EXPORT = qw(confess croak carp); our @EXPORT_OK = qw(cluck verbose longmess shortmess); our @EXPORT_FAIL = qw(verbose); # hook to enable verbose mode # The members of %Internal are packages that are internal to perl. # Carp will not report errors from within these packages if it # can. The members of %CarpInternal are internal to Perl's warning # system. Carp will not report errors from within these packages # either, and will not report calls *to* these packages for carp and # croak. They replace $CarpLevel, which is deprecated. The # $Max(EvalLen|(Arg(Len|Nums)) variables are used to specify how the eval # text and function arguments should be formatted when printed. our %CarpInternal; our %Internal; # disable these by default, so they can live w/o require Carp $CarpInternal{Carp}++; $CarpInternal{warnings}++; $Internal{Exporter}++; $Internal{'Exporter::Heavy'}++; # if the caller specifies verbose usage ("perl -MCarp=verbose script.pl") # then the following method will be called by the Exporter which knows # to do this thanks to @EXPORT_FAIL, above. $_[1] will contain the word # 'verbose'. sub export_fail { shift; $Verbose = shift if $_[0] eq 'verbose'; @_ } sub _cgc { no strict 'refs'; return \&{"CORE::GLOBAL::caller"} if defined &{"CORE::GLOBAL::caller"}; return; } sub longmess { local($!, $^E); # Icky backwards compatibility wrapper. :-( # # The story is that the original implementation hard-coded the # number of call levels to go back, so calls to longmess were off # by one. Other code began calling longmess and expecting this # behaviour, so the replacement has to emulate that behaviour. my $cgc = _cgc(); my $call_pack = $cgc ? $cgc->() : caller(); if ( $Internal{$call_pack} or $CarpInternal{$call_pack} ) { return longmess_heavy(@_); } else { local $CarpLevel = $CarpLevel + 1; return longmess_heavy(@_); } } our @CARP_NOT; sub shortmess { local($!, $^E); my $cgc = _cgc(); # Icky backwards compatibility wrapper. :-( local @CARP_NOT = $cgc ? $cgc->() : caller(); shortmess_heavy(@_); } sub croak { die shortmess @_ } sub confess { die longmess @_ } sub carp { warn shortmess @_ } sub cluck { warn longmess @_ } BEGIN { if("$]" >= 5.015002 || ("$]" >= 5.014002 && "$]" < 5.015) || ("$]" >= 5.012005 && "$]" < 5.013)) { *CALLER_OVERRIDE_CHECK_OK = sub () { 1 }; } else { *CALLER_OVERRIDE_CHECK_OK = sub () { 0 }; } } sub caller_info { my $i = shift(@_) + 1; my %call_info; my $cgc = _cgc(); { # Some things override caller() but forget to implement the # @DB::args part of it, which we need. We check for this by # pre-populating @DB::args with a sentinel which no-one else # has the address of, so that we can detect whether @DB::args # has been properly populated. However, on earlier versions # of perl this check tickles a bug in CORE::caller() which # leaks memory. So we only check on fixed perls. @DB::args = \$i if CALLER_OVERRIDE_CHECK_OK; package DB; @call_info{ qw(pack file line sub has_args wantarray evaltext is_require) } = $cgc ? $cgc->($i) : caller($i); } unless ( defined $call_info{file} ) { return (); } my $sub_name = Carp::get_subname( \%call_info ); if ( $call_info{has_args} ) { # Guard our serialization of the stack from stack refcounting bugs # NOTE this is NOT a complete solution, we cannot 100% guard against # these bugs. However in many cases Perl *is* capable of detecting # them and throws an error when it does. Unfortunately serializing # the arguments on the stack is a perfect way of finding these bugs, # even when they would not affect normal program flow that did not # poke around inside the stack. Inside of Carp.pm it makes little # sense reporting these bugs, as Carp's job is to report the callers # errors, not the ones it might happen to tickle while doing so. # See: https://rt.perl.org/Public/Bug/Display.html?id=131046 # and: https://rt.perl.org/Public/Bug/Display.html?id=52610 # for more details and discussion. - Yves my @args = map { my $arg; local $@= $@; eval { $arg = $_; 1; } or do { $arg = '** argument not available anymore **'; }; $arg; } @DB::args; if (CALLER_OVERRIDE_CHECK_OK && @args == 1 && ref $args[0] eq ref \$i && $args[0] == \$i ) { @args = (); # Don't let anyone see the address of $i local $@; my $where = eval { my $func = $cgc or return ''; my $gv = (_fetch_sub B => 'svref_2object' or return '') ->($func)->GV; my $package = $gv->STASH->NAME; my $subname = $gv->NAME; return unless defined $package && defined $subname; # returning CORE::GLOBAL::caller isn't useful for tracing the cause: return if $package eq 'CORE::GLOBAL' && $subname eq 'caller'; " in &${package}::$subname"; } || ''; @args = "** Incomplete caller override detected$where; \@DB::args were not set **"; } else { my $overflow; if ( $MaxArgNums and @args > $MaxArgNums ) { # More than we want to show? $#args = $MaxArgNums - 1; $overflow = 1; } @args = map { Carp::format_arg($_) } @args; if ($overflow) { push @args, '...'; } } # Push the args onto the subroutine $sub_name .= '(' . join( ', ', @args ) . ')'; } $call_info{sub_name} = $sub_name; return wantarray() ? %call_info : \%call_info; } # Transform an argument to a function into a string. our $in_recurse; sub format_arg { my $arg = shift; if ( my $pack= ref($arg) ) { # legitimate, let's not leak it. if (!$in_recurse && _maybe_isa( $arg, 'UNIVERSAL' ) && do { local $@; local $in_recurse = 1; local $SIG{__DIE__} = sub{}; eval {$arg->can('CARP_TRACE') } }) { return $arg->CARP_TRACE(); } elsif (!$in_recurse && defined($RefArgFormatter) && do { local $@; local $in_recurse = 1; local $SIG{__DIE__} = sub{}; eval {$arg = $RefArgFormatter->($arg); 1} }) { return $arg; } else { # Argument may be blessed into a class with overloading, and so # might have an overloaded stringification. We don't want to # risk getting the overloaded stringification, so we need to # use _StrVal, our overload::StrVal()-equivalent. return _StrVal $arg; } } return "undef" if !defined($arg); downgrade($arg, 1); return $arg if !(UTF8_REGEXP_PROBLEM && is_utf8($arg)) && $arg =~ /\A-?[0-9]+(?:\.[0-9]*)?(?:[eE][-+]?[0-9]+)?\z/; my $suffix = ""; if ( 2 < $MaxArgLen and $MaxArgLen < length($arg) ) { substr ( $arg, $MaxArgLen - 3 ) = ""; $suffix = "..."; } if(UTF8_REGEXP_PROBLEM && is_utf8($arg)) { for(my $i = length($arg); $i--; ) { my $c = substr($arg, $i, 1); my $x = substr($arg, 0, 0); # work around bug on Perl 5.8.{1,2} if($c eq "\"" || $c eq "\\" || $c eq "\$" || $c eq "\@") { substr $arg, $i, 0, "\\"; next; } my $o = ord($c); substr $arg, $i, 1, sprintf("\\x{%x}", $o) unless is_safe_printable_codepoint($o); } } else { $arg =~ s/([\"\\\$\@])/\\$1/g; # This is all the ASCII printables spelled-out. It is portable to all # Perl versions and platforms (such as EBCDIC). There are other more # compact ways to do this, but may not work everywhere every version. $arg =~ s/([^ !"#\$\%\&'()*+,\-.\/0123456789:;<=>?\@ABCDEFGHIJKLMNOPQRSTUVWXYZ\[\\\]^_`abcdefghijklmnopqrstuvwxyz\{|}~])/sprintf("\\x{%x}",ord($1))/eg; } downgrade($arg, 1); return "\"".$arg."\"".$suffix; } sub Regexp::CARP_TRACE { my $arg = "$_[0]"; downgrade($arg, 1); if(UTF8_REGEXP_PROBLEM && is_utf8($arg)) { for(my $i = length($arg); $i--; ) { my $o = ord(substr($arg, $i, 1)); my $x = substr($arg, 0, 0); # work around bug on Perl 5.8.{1,2} substr $arg, $i, 1, sprintf("\\x{%x}", $o) unless is_safe_printable_codepoint($o); } } else { # See comment in format_arg() about this same regex. $arg =~ s/([^ !"#\$\%\&'()*+,\-.\/0123456789:;<=>?\@ABCDEFGHIJKLMNOPQRSTUVWXYZ\[\\\]^_`abcdefghijklmnopqrstuvwxyz\{|}~])/sprintf("\\x{%x}",ord($1))/eg; } downgrade($arg, 1); my $suffix = ""; if($arg =~ /\A\(\?\^?([a-z]*)(?:-[a-z]*)?:(.*)\)\z/s) { ($suffix, $arg) = ($1, $2); } if ( 2 < $MaxArgLen and $MaxArgLen < length($arg) ) { substr ( $arg, $MaxArgLen - 3 ) = ""; $suffix = "...".$suffix; } return "qr($arg)$suffix"; } # Takes an inheritance cache and a package and returns # an anon hash of known inheritances and anon array of # inheritances which consequences have not been figured # for. sub get_status { my $cache = shift; my $pkg = shift; $cache->{$pkg} ||= [ { $pkg => $pkg }, [ trusts_directly($pkg) ] ]; return @{ $cache->{$pkg} }; } # Takes the info from caller() and figures out the name of # the sub/require/eval sub get_subname { my $info = shift; if ( defined( $info->{evaltext} ) ) { my $eval = $info->{evaltext}; if ( $info->{is_require} ) { return "require $eval"; } else { $eval =~ s/([\\\'])/\\$1/g; return "eval '" . str_len_trim( $eval, $MaxEvalLen ) . "'"; } } # this can happen on older perls when the sub (or the stash containing it) # has been deleted if ( !defined( $info->{sub} ) ) { return '__ANON__::__ANON__'; } return ( $info->{sub} eq '(eval)' ) ? 'eval {...}' : $info->{sub}; } # Figures out what call (from the point of view of the caller) # the long error backtrace should start at. sub long_error_loc { my $i; my $lvl = $CarpLevel; { ++$i; my $cgc = _cgc(); my @caller = $cgc ? $cgc->($i) : caller($i); my $pkg = $caller[0]; unless ( defined($pkg) ) { # This *shouldn't* happen. if (%Internal) { local %Internal; $i = long_error_loc(); last; } elsif (defined $caller[2]) { # this can happen when the stash has been deleted # in that case, just assume that it's a reasonable place to # stop (the file and line data will still be intact in any # case) - the only issue is that we can't detect if the # deleted package was internal (so don't do that then) # -doy redo unless 0 > --$lvl; last; } else { return 2; } } redo if $CarpInternal{$pkg}; redo unless 0 > --$lvl; redo if $Internal{$pkg}; } return $i - 1; } sub longmess_heavy { if ( ref( $_[0] ) ) { # don't break references as exceptions return wantarray ? @_ : $_[0]; } my $i = long_error_loc(); return ret_backtrace( $i, @_ ); } BEGIN { if("$]" >= 5.017004) { # The LAST_FH constant is a reference to the variable. $Carp::{LAST_FH} = \eval '\${^LAST_FH}'; } else { eval '*LAST_FH = sub () { 0 }'; } } # Returns a full stack backtrace starting from where it is # told. sub ret_backtrace { my ( $i, @error ) = @_; my $mess; my $err = join '', @error; $i++; my $tid_msg = ''; if ( defined &threads::tid ) { my $tid = threads->tid; $tid_msg = " thread $tid" if $tid; } my %i = caller_info($i); $mess = "$err at $i{file} line $i{line}$tid_msg"; if( $. ) { # Use ${^LAST_FH} if available. if (LAST_FH) { if (${+LAST_FH}) { $mess .= sprintf ", <%s> %s %d", *${+LAST_FH}{NAME}, ($/ eq "\n" ? "line" : "chunk"), $. } } else { local $@ = ''; local $SIG{__DIE__}; eval { CORE::die; }; if($@ =~ /^Died at .*(, <.*?> (?:line|chunk) \d+).$/ ) { $mess .= $1; } } } $mess .= "\.\n"; while ( my %i = caller_info( ++$i ) ) { $mess .= "\t$i{sub_name} called at $i{file} line $i{line}$tid_msg\n"; } return $mess; } sub ret_summary { my ( $i, @error ) = @_; my $err = join '', @error; $i++; my $tid_msg = ''; if ( defined &threads::tid ) { my $tid = threads->tid; $tid_msg = " thread $tid" if $tid; } my %i = caller_info($i); return "$err at $i{file} line $i{line}$tid_msg\.\n"; } sub short_error_loc { # You have to create your (hash)ref out here, rather than defaulting it # inside trusts *on a lexical*, as you want it to persist across calls. # (You can default it on $_[2], but that gets messy) my $cache = {}; my $i = 1; my $lvl = $CarpLevel; { my $cgc = _cgc(); my $called = $cgc ? $cgc->($i) : caller($i); $i++; my $caller = $cgc ? $cgc->($i) : caller($i); if (!defined($caller)) { my @caller = $cgc ? $cgc->($i) : caller($i); if (@caller) { # if there's no package but there is other caller info, then # the package has been deleted - treat this as a valid package # in this case redo if defined($called) && $CarpInternal{$called}; redo unless 0 > --$lvl; last; } else { return 0; } } redo if $Internal{$caller}; redo if $CarpInternal{$caller}; redo if $CarpInternal{$called}; redo if trusts( $called, $caller, $cache ); redo if trusts( $caller, $called, $cache ); redo unless 0 > --$lvl; } return $i - 1; } sub shortmess_heavy { return longmess_heavy(@_) if $Verbose; return @_ if ref( $_[0] ); # don't break references as exceptions my $i = short_error_loc(); if ($i) { ret_summary( $i, @_ ); } else { longmess_heavy(@_); } } # If a string is too long, trims it with ... sub str_len_trim { my $str = shift; my $max = shift || 0; if ( 2 < $max and $max < length($str) ) { substr( $str, $max - 3 ) = '...'; } return $str; } # Takes two packages and an optional cache. Says whether the # first inherits from the second. # # Recursive versions of this have to work to avoid certain # possible endless loops, and when following long chains of # inheritance are less efficient. sub trusts { my $child = shift; my $parent = shift; my $cache = shift; my ( $known, $partial ) = get_status( $cache, $child ); # Figure out consequences until we have an answer while ( @$partial and not exists $known->{$parent} ) { my $anc = shift @$partial; next if exists $known->{$anc}; $known->{$anc}++; my ( $anc_knows, $anc_partial ) = get_status( $cache, $anc ); my @found = keys %$anc_knows; @$known{@found} = (); push @$partial, @$anc_partial; } return exists $known->{$parent}; } # Takes a package and gives a list of those trusted directly sub trusts_directly { my $class = shift; no strict 'refs'; my $stash = \%{"$class\::"}; for my $var (qw/ CARP_NOT ISA /) { # Don't try using the variable until we know it exists, # to avoid polluting the caller's namespace. if ( $stash->{$var} && ref \$stash->{$var} eq 'GLOB' && *{$stash->{$var}}{ARRAY} && @{$stash->{$var}} ) { return @{$stash->{$var}} } } return; } if(!defined($warnings::VERSION) || do { no warnings "numeric"; $warnings::VERSION < 1.03 }) { # Very old versions of warnings.pm import from Carp. This can go # wrong due to the circular dependency. If Carp is invoked before # warnings, then Carp starts by loading warnings, then warnings # tries to import from Carp, and gets nothing because Carp is in # the process of loading and hasn't defined its import method yet. # So we work around that by manually exporting to warnings here. no strict "refs"; *{"warnings::$_"} = \&$_ foreach @EXPORT; } 1; __END__ # # Documentation is at the __END__ # package DB; # "private" globals my ($running, $ready, $deep, $usrctxt, $evalarg, @stack, @saved, @skippkg, @clients); my $preeval = {}; my $posteval = {}; my $ineval = {}; #### # # Globals - must be defined at startup so that clients can refer to # them right after a C # #### BEGIN { # these are hardcoded in perl source (some are magical) $DB::sub = ''; # name of current subroutine %DB::sub = (); # "filename:fromline-toline" for every known sub $DB::single = 0; # single-step flag (set it to 1 to enable stops in BEGIN/use) $DB::signal = 0; # signal flag (will cause a stop at the next line) $DB::trace = 0; # are we tracing through subroutine calls? @DB::args = (); # arguments of current subroutine or @ARGV array @DB::dbline = (); # list of lines in currently loaded file %DB::dbline = (); # actions in current file (keyed by line number) @DB::ret = (); # return value of last sub executed in list context $DB::ret = ''; # return value of last sub executed in scalar context # other "public" globals $DB::package = ''; # current package space $DB::filename = ''; # current filename $DB::subname = ''; # currently executing sub (fully qualified name) $DB::lineno = ''; # current line number $DB::VERSION = $DB::VERSION = '1.08'; # initialize private globals to avoid warnings $running = 1; # are we running, or are we stopped? @stack = (0); @clients = (); $deep = 1000; $ready = 0; @saved = (); @skippkg = (); $usrctxt = ''; $evalarg = ''; } #### # entry point for all subroutine calls # sub sub { push(@stack, $DB::single); $DB::single &= 1; $DB::single |= 4 if $#stack == $deep; if ($DB::sub eq 'DESTROY' or substr($DB::sub, -9) eq '::DESTROY' or not defined wantarray) { &$DB::sub; $DB::single |= pop(@stack); $DB::ret = undef; } elsif (wantarray) { @DB::ret = &$DB::sub; $DB::single |= pop(@stack); @DB::ret; } else { $DB::ret = &$DB::sub; $DB::single |= pop(@stack); $DB::ret; } } #### # this is called by perl for every statement # sub DB { return unless $ready; &save; ($DB::package, $DB::filename, $DB::lineno) = caller; return if @skippkg and grep { $_ eq $DB::package } @skippkg; $usrctxt = "package $DB::package;"; # this won't let them modify, alas local(*DB::dbline) = "::_<$DB::filename"; my ($stop, $action); if (($stop,$action) = split(/\0/,$DB::dbline{$DB::lineno})) { if ($stop eq '1') { $DB::signal |= 1; } else { $stop = 0 unless $stop; # avoid un_init warning $evalarg = "\$DB::signal |= do { $stop; }"; &eval; $DB::dbline{$DB::lineno} =~ s/;9($|\0)/$1/; # clear any temp breakpt } } if ($DB::single || $DB::trace || $DB::signal) { $DB::subname = ($DB::sub =~ /\'|::/) ? $DB::sub : "${DB::package}::$DB::sub"; #'; DB->loadfile($DB::filename, $DB::lineno); } $evalarg = $action, &eval if $action; if ($DB::single || $DB::signal) { _outputall($#stack . " levels deep in subroutine calls.\n") if $DB::single & 4; $DB::single = 0; $DB::signal = 0; $running = 0; &eval if ($evalarg = DB->prestop); my $c; for $c (@clients) { # perform any client-specific prestop actions &eval if ($evalarg = $c->cprestop); # Now sit in an event loop until something sets $running do { $c->idle; # call client event loop; must not block if ($running == 2) { # client wants something eval-ed &eval if ($evalarg = $c->evalcode); $running = 0; } } until $running; # perform any client-specific poststop actions &eval if ($evalarg = $c->cpoststop); } &eval if ($evalarg = DB->poststop); } ($@, $!, $,, $/, $\, $^W) = @saved; (); } #### # this takes its argument via $evalarg to preserve current @_ # sub eval { ($@, $!, $,, $/, $\, $^W) = @saved; eval "$usrctxt $evalarg; &DB::save"; _outputall($@) if $@; } ############################################################################### # no compile-time subroutine call allowed before this point # ############################################################################### use strict; # this can run only after DB() and sub() are defined sub save { @saved = ($@, $!, $,, $/, $\, $^W); $, = ""; $/ = "\n"; $\ = ""; $^W = 0; } sub catch { for (@clients) { $_->awaken; } $DB::signal = 1; $ready = 1; } #### # # Client callable (read inheritable) methods defined after this point # #### sub register { my $s = shift; $s = _clientname($s) if ref($s); push @clients, $s; } sub done { my $s = shift; $s = _clientname($s) if ref($s); @clients = grep {$_ ne $s} @clients; $s->cleanup; # $running = 3 unless @clients; exit(0) unless @clients; } sub _clientname { my $name = shift; "$name" =~ /^(.+)=[A-Z]+\(.+\)$/; return $1; } sub next { my $s = shift; $DB::single = 2; $running = 1; } sub step { my $s = shift; $DB::single = 1; $running = 1; } sub cont { my $s = shift; my $i = shift; $s->set_tbreak($i) if $i; for ($i = 0; $i <= $#stack;) { $stack[$i++] &= ~1; } $DB::single = 0; $running = 1; } #### # XXX caller must experimentally determine $i (since it depends # on how many client call frames are between this call and the DB call). # Such is life. # sub ret { my $s = shift; my $i = shift; # how many levels to get to DB sub $i = 0 unless defined $i; $stack[$#stack-$i] |= 1; $DB::single = 0; $running = 1; } #### # XXX caller must experimentally determine $start (since it depends # on how many client call frames are between this call and the DB call). # Such is life. # sub backtrace { my $self = shift; my $start = shift; my($p,$f,$l,$s,$h,$w,$e,$r,$a, @a, @ret,$i); $start = 1 unless $start; for ($i = $start; ($p,$f,$l,$s,$h,$w,$e,$r) = caller($i); $i++) { @a = @DB::args; for (@a) { s/'/\\'/g; s/([^\0]*)/'$1'/ unless /^-?[\d.]+$/; require 'meta_notation.pm'; $_ = _meta_notation($_) if /[[:^print:]]/a; } $w = $w ? '@ = ' : '$ = '; $a = $h ? '(' . join(', ', @a) . ')' : ''; $e =~ s/\n\s*\;\s*\Z// if $e; $e =~ s/[\\\']/\\$1/g if $e; if ($r) { $s = "require '$e'"; } elsif (defined $r) { $s = "eval '$e'"; } elsif ($s eq '(eval)') { $s = "eval {...}"; } $f = "file '$f'" unless $f eq '-e'; push @ret, "$w&$s$a from $f line $l"; last if $DB::signal; } return @ret; } sub _outputall { my $c; for $c (@clients) { $c->output(@_); } } sub trace_toggle { my $s = shift; $DB::trace = !$DB::trace; } #### # without args: returns all defined subroutine names # with subname args: returns a listref [file, start, end] # sub subs { my $s = shift; if (@_) { my(@ret) = (); while (@_) { my $name = shift; push @ret, [$DB::sub{$name} =~ /^(.*)\:(\d+)-(\d+)$/] if exists $DB::sub{$name}; } return @ret; } return keys %DB::sub; } #### # first argument is a filename whose subs will be returned # if a filename is not supplied, all subs in the current # filename are returned. # sub filesubs { my $s = shift; my $fname = shift; $fname = $DB::filename unless $fname; return grep { $DB::sub{$_} =~ /^$fname/ } keys %DB::sub; } #### # returns a list of all filenames that DB knows about # sub files { my $s = shift; my(@f) = grep(m|^_<|, keys %main::); return map { substr($_,2) } @f; } #### # returns reference to an array holding the lines in currently # loaded file # sub lines { my $s = shift; return \@DB::dbline; } #### # loadfile($file, $line) # sub loadfile { my $s = shift; my($file, $line) = @_; if (!defined $main::{'_<' . $file}) { my $try; if (($try) = grep(m|^_<.*$file|, keys %main::)) { $file = substr($try,2); } } if (defined($main::{'_<' . $file})) { my $c; # _outputall("Loading file $file.."); *DB::dbline = "::_<$file"; $DB::filename = $file; for $c (@clients) { # print "2 ", $file, '|', $line, "\n"; $c->showfile($file, $line); } return $file; } return undef; } sub lineevents { my $s = shift; my $fname = shift; my(%ret) = (); my $i; $fname = $DB::filename unless $fname; local(*DB::dbline) = "::_<$fname"; for ($i = 1; $i <= $#DB::dbline; $i++) { $ret{$i} = [$DB::dbline[$i], split(/\0/, $DB::dbline{$i})] if defined $DB::dbline{$i}; } return %ret; } sub set_break { my $s = shift; my $i = shift; my $cond = shift; $i ||= $DB::lineno; $cond ||= '1'; $i = _find_subline($i) if ($i =~ /\D/); $s->output("Subroutine not found.\n") unless $i; if ($i) { if ($DB::dbline[$i] == 0) { $s->output("Line $i not breakable.\n"); } else { $DB::dbline{$i} =~ s/^[^\0]*/$cond/; } } } sub set_tbreak { my $s = shift; my $i = shift; $i = _find_subline($i) if ($i =~ /\D/); $s->output("Subroutine not found.\n") unless $i; if ($i) { if ($DB::dbline[$i] == 0) { $s->output("Line $i not breakable.\n"); } else { $DB::dbline{$i} =~ s/($|\0)/;9$1/; # add one-time-only b.p. } } } sub _find_subline { my $name = shift; $name =~ s/\'/::/; $name = "${DB::package}\:\:" . $name if $name !~ /::/; $name = "main" . $name if substr($name,0,2) eq "::"; my($fname, $from, $to) = ($DB::sub{$name} =~ /^(.*):(\d+)-(\d+)$/); if ($from) { local *DB::dbline = "::_<$fname"; ++$from while $DB::dbline[$from] == 0 && $from < $to; return $from; } return undef; } sub clr_breaks { my $s = shift; my $i; if (@_) { while (@_) { $i = shift; $i = _find_subline($i) if ($i =~ /\D/); $s->output("Subroutine not found.\n") unless $i; if (defined $DB::dbline{$i}) { $DB::dbline{$i} =~ s/^[^\0]+//; if ($DB::dbline{$i} =~ s/^\0?$//) { delete $DB::dbline{$i}; } } } } else { for ($i = 1; $i <= $#DB::dbline ; $i++) { if (defined $DB::dbline{$i}) { $DB::dbline{$i} =~ s/^[^\0]+//; if ($DB::dbline{$i} =~ s/^\0?$//) { delete $DB::dbline{$i}; } } } } } sub set_action { my $s = shift; my $i = shift; my $act = shift; $i = _find_subline($i) if ($i =~ /\D/); $s->output("Subroutine not found.\n") unless $i; if ($i) { if ($DB::dbline[$i] == 0) { $s->output("Line $i not actionable.\n"); } else { $DB::dbline{$i} =~ s/\0[^\0]*//; $DB::dbline{$i} .= "\0" . $act; } } } sub clr_actions { my $s = shift; my $i; if (@_) { while (@_) { my $i = shift; $i = _find_subline($i) if ($i =~ /\D/); $s->output("Subroutine not found.\n") unless $i; if ($i && $DB::dbline[$i] != 0) { $DB::dbline{$i} =~ s/\0[^\0]*//; delete $DB::dbline{$i} if $DB::dbline{$i} =~ s/^\0?$//; } } } else { for ($i = 1; $i <= $#DB::dbline ; $i++) { if (defined $DB::dbline{$i}) { $DB::dbline{$i} =~ s/\0[^\0]*//; delete $DB::dbline{$i} if $DB::dbline{$i} =~ s/^\0?$//; } } } } sub prestop { my ($client, $val) = @_; return defined($val) ? $preeval->{$client} = $val : $preeval->{$client}; } sub poststop { my ($client, $val) = @_; return defined($val) ? $posteval->{$client} = $val : $posteval->{$client}; } # # "pure virtual" methods # # client-specific pre/post-stop actions. sub cprestop {} sub cpoststop {} # client complete startup sub awaken {} sub skippkg { my $s = shift; push @skippkg, @_ if @_; } sub evalcode { my ($client, $val) = @_; if (defined $val) { $running = 2; # hand over to DB() to evaluate in its context $ineval->{$client} = $val; } return $ineval->{$client}; } sub ready { my $s = shift; return $ready = 1; } # stubs sub init {} sub stop {} sub idle {} sub cleanup {} sub output {} # # client init # for (@clients) { $_->init } $SIG{'INT'} = \&DB::catch; # disable this if stepping through END blocks is desired # (looks scary and deconstructivist with Swat) END { $ready = 0 } 1; __END__ package FileHandle; use 5.006; use strict; our($VERSION, @ISA, @EXPORT, @EXPORT_OK); $VERSION = "2.03"; require IO::File; @ISA = qw(IO::File); @EXPORT = qw(_IOFBF _IOLBF _IONBF); @EXPORT_OK = qw( pipe autoflush output_field_separator output_record_separator input_record_separator input_line_number format_page_number format_lines_per_page format_lines_left format_name format_top_name format_line_break_characters format_formfeed print printf getline getlines ); # # Everything we're willing to export, we must first import. # IO::Handle->import( grep { !defined(&$_) } @EXPORT, @EXPORT_OK ); # # Some people call "FileHandle::function", so all the functions # that were in the old FileHandle class must be imported, too. # { no strict 'refs'; my %import = ( 'IO::Handle' => [qw(DESTROY new_from_fd fdopen close fileno getc ungetc gets eof flush error clearerr setbuf setvbuf _open_mode_string)], 'IO::Seekable' => [qw(seek tell getpos setpos)], 'IO::File' => [qw(new new_tmpfile open)] ); for my $pkg (keys %import) { for my $func (@{$import{$pkg}}) { my $c = *{"${pkg}::$func"}{CODE} or die "${pkg}::$func missing"; *$func = $c; } } } # # Specialized importer for Fcntl magic. # sub import { my $pkg = shift; my $callpkg = caller; require Exporter; Exporter::export($pkg, $callpkg, @_); # # If the Fcntl extension is available, # export its constants. # eval { require Fcntl; Exporter::export('Fcntl', $callpkg); }; } ################################################ # This is the only exported function we define; # the rest come from other classes. # sub pipe { my $r = IO::Handle->new; my $w = IO::Handle->new; CORE::pipe($r, $w) or return undef; ($r, $w); } # Rebless standard file handles bless *STDIN{IO}, "FileHandle" if ref *STDIN{IO} eq "IO::Handle"; bless *STDOUT{IO}, "FileHandle" if ref *STDOUT{IO} eq "IO::Handle"; bless *STDERR{IO}, "FileHandle" if ref *STDERR{IO} eq "IO::Handle"; 1; __END__ package filetest; our $VERSION = '1.03'; $filetest::hint_bits = 0x00400000; # HINT_FILETEST_ACCESS sub import { if ( $_[1] eq 'access' ) { $^H |= $filetest::hint_bits; } else { die "filetest: the only implemented subpragma is 'access'.\n"; } } sub unimport { if ( $_[1] eq 'access' ) { $^H &= ~$filetest::hint_bits; } else { die "filetest: the only implemented subpragma is 'access'.\n"; } } 1; package bignum; use 5.010; use strict; use warnings; our $VERSION = '0.49'; use Exporter; our @ISA = qw( bigint ); our @EXPORT_OK = qw( PI e bpi bexp hex oct ); our @EXPORT = qw( inf NaN ); use overload; use bigint (); ############################################################################## BEGIN { *inf = \&bigint::inf; *NaN = \&bigint::NaN; *hex = \&bigint::hex; *oct = \&bigint::oct; } # These are all alike, and thus faked by AUTOLOAD my @faked = qw/round_mode accuracy precision div_scale/; our ($AUTOLOAD, $_lite); # _lite for testsuite sub AUTOLOAD { my $name = $AUTOLOAD; $name =~ s/.*:://; # split package no strict 'refs'; foreach my $n (@faked) { if ($n eq $name) { *{"bignum::$name"} = sub { my $self = shift; no strict 'refs'; if (defined $_[0]) { Math::BigInt->$name($_[0]); return Math::BigFloat->$name($_[0]); } return Math::BigInt->$name(); }; return &$name; } } # delayed load of Carp and avoid recursion require Carp; Carp::croak ("Can't call bignum\-\>$name, not a valid method"); } sub unimport { $^H{bignum} = undef; # no longer in effect overload::remove_constant('binary', '', 'float', '', 'integer'); } sub in_effect { my $level = shift || 0; my $hinthash = (caller($level))[10]; $hinthash->{bignum}; } ############################################################################# sub import { my $self = shift; $^H{bignum} = 1; # we are in effect # for newer Perls override hex() and oct() with a lexical version: if ($] > 5.009004) { bigint::_override(); } # some defaults my $lib = ''; my $lib_kind = 'try'; my $upgrade = 'Math::BigFloat'; my $downgrade = 'Math::BigInt'; my @import = (':constant'); # drive it w/ constant my @a = @_; my $l = scalar @_; my $j = 0; my ($ver, $trace); # version? trace? my ($a, $p); # accuracy, precision for (my $i = 0; $i < $l; $i++, $j++) { if ($_[$i] eq 'upgrade') { # this causes upgrading $upgrade = $_[$i + 1]; # or undef to disable my $s = 2; $s = 1 if @a - $j < 2; # avoid "can not modify non-existent..." splice @a, $j, $s; $j -= $s; $i++; } elsif ($_[$i] eq 'downgrade') { # this causes downgrading $downgrade = $_[$i + 1]; # or undef to disable my $s = 2; $s = 1 if @a - $j < 2; # avoid "can not modify non-existent..." splice @a, $j, $s; $j -= $s; $i++; } elsif ($_[$i] =~ /^(l|lib|try|only)$/) { # this causes a different low lib to take care... $lib_kind = $1; $lib_kind = 'lib' if $lib_kind eq 'l'; $lib = $_[$i + 1] || ''; my $s = 2; $s = 1 if @a - $j < 2; # avoid "can not modify non-existent..." splice @a, $j, $s; $j -= $s; $i++; } elsif ($_[$i] =~ /^(a|accuracy)$/) { $a = $_[$i + 1]; my $s = 2; $s = 1 if @a - $j < 2; # avoid "can not modify non-existent..." splice @a, $j, $s; $j -= $s; $i++; } elsif ($_[$i] =~ /^(p|precision)$/) { $p = $_[$i + 1]; my $s = 2; $s = 1 if @a - $j < 2; # avoid "can not modify non-existent..." splice @a, $j, $s; $j -= $s; $i++; } elsif ($_[$i] =~ /^(v|version)$/) { $ver = 1; splice @a, $j, 1; $j--; } elsif ($_[$i] =~ /^(t|trace)$/) { $trace = 1; splice @a, $j, 1; $j--; } elsif ($_[$i] !~ /^(PI|e|bexp|bpi|hex|oct)\z/) { die ("unknown option $_[$i]"); } } my $class; $_lite = 0; # using M::BI::L ? if ($trace) { require Math::BigInt::Trace; $class = 'Math::BigInt::Trace'; $upgrade = 'Math::BigFloat::Trace'; } else { # see if we can find Math::BigInt::Lite if (!defined $a && !defined $p) { # rounding won't work to well local @INC = @INC; pop @INC if $INC[-1] eq '.'; if (eval { require Math::BigInt::Lite; 1 }) { @import = (); # :constant in Lite, not MBI Math::BigInt::Lite->import(':constant'); $_lite = 1; # signal okay } } require Math::BigInt if $_lite == 0; # not already loaded? $class = 'Math::BigInt'; # regardless of MBIL or not } push @import, $lib_kind => $lib if $lib ne ''; # Math::BigInt::Trace or plain Math::BigInt $class->import(@import, upgrade => $upgrade); if ($trace) { require Math::BigFloat::Trace; $class = 'Math::BigFloat::Trace'; $downgrade = 'Math::BigInt::Trace'; } else { require Math::BigFloat; $class = 'Math::BigFloat'; } $class->import(':constant', 'downgrade', $downgrade); bignum->accuracy($a) if defined $a; bignum->precision($p) if defined $p; if ($ver) { print "bignum\t\t\t v$VERSION\n"; print "Math::BigInt::Lite\t v$Math::BigInt::Lite::VERSION\n" if $_lite; print "Math::BigInt\t\t v$Math::BigInt::VERSION"; my $config = Math::BigInt->config(); print " lib => $config->{lib} v$config->{lib_version}\n"; print "Math::BigFloat\t\t v$Math::BigFloat::VERSION\n"; exit; } # Take care of octal/hexadecimal constants overload::constant binary => sub { bigint::_binary_constant(shift); }; # if another big* was already loaded: my ($package) = caller(); no strict 'refs'; if (!defined *{"${package}::inf"}) { $self->export_to_level(1, $self, @a); # export inf and NaN } } sub PI () { Math::BigFloat->new('3.141592653589793238462643383279502884197'); } sub e () { Math::BigFloat->new('2.718281828459045235360287471352662497757'); } sub bpi ($) { Math::BigFloat->bpi(@_); } sub bexp ($$) { my $x = Math::BigFloat->new($_[0]); $x->bexp($_[1]); } 1; __END__ # !!!!!!! INTERNAL PERL USE ONLY !!!!!!! # This helper module is for internal use by core Perl only. This module is # subject to change or removal at any time without notice. Don't use it # directly. Use the public module instead. package _charnames; use strict; use warnings; our $VERSION = '1.45'; use unicore::Name; # mktables-generated algorithmically-defined names use bytes (); # for $bytes::hint_bits use re "/aa"; # Everything in here should be ASCII $Carp::Internal{ (__PACKAGE__) } = 1; # Translate between Unicode character names and their code points. This is a # submodule of package , used to allow \N{...} to be autoloaded, # but it was decided not to autoload the various functions in charnames; the # splitting allows this behavior. # # The official names with their code points are stored in a table in # lib/unicore/Name.pl which is read in as a large string (almost 3/4 Mb in # Unicode 6.0). Each code point/name combination is separated by a \n in the # string. (Some of the CJK and the Hangul syllable names are instead # determined algorithmically via subroutines stored instead in # lib/unicore/Name.pm). Because of the large size of this table, it isn't # converted into hashes for faster lookup. # # But, user defined aliases are stored in their own hashes, as are Perl # extensions to the official names. These are checked first before looking at # the official table. # # Basically, the table is grepped for the input code point (viacode()) or # name (the other functions), and the corresponding value on the same line is # returned. The grepping is done by turning the input into a regular # expression. Thus, the same table does double duty, used by both name and # code point lookup. (If we were to have hashes, we would need two, one for # each lookup direction.) # # For loose name matching, the logical thing would be to have a table # with all the ignorable characters squeezed out, and then grep it with the # similiarly-squeezed input name. (And this is in fact how the lookups are # done with the small Perl extension hashes.) But since we need to be able to # go from code point to official name, the original table would still need to # exist. Due to the large size of the table, it was decided to not read # another very large string into memory for a second table. Instead, the # regular expression of the input name is modified to have optional spaces and # dashes between characters. For example, in strict matching, the regular # expression would be: # qr/\tDIGIT ONE$/m # Under loose matching, the blank would be squeezed out, and the re would be: # qr/\tD[- ]?I[- ]?G[- ]?I[- ]?T[- ]?O[- ]?N[- ]?E$/m # which matches a blank or dash between any characters in the official table. # # This is also how script lookup is done. Basically the re looks like # qr/ (?:LATIN|GREEK|CYRILLIC) (?:SMALL )?LETTER $name/ # where $name is the loose or strict regex for the remainder of the name. # The hashes are stored as utf8 strings. This makes it easier to deal with # sequences. I (khw) also tried making Name.pl utf8, but it slowed things # down by a factor of 7. I then tried making Name.pl store the ut8 # equivalents but not calling them utf8. That led to similar speed as leaving # it alone, but since that is harder for a human to parse, I left it as-is. my %system_aliases = ( 'SINGLE-SHIFT 2' => chr utf8::unicode_to_native(0x8E), 'SINGLE-SHIFT 3' => chr utf8::unicode_to_native(0x8F), 'PRIVATE USE 1' => chr utf8::unicode_to_native(0x91), 'PRIVATE USE 2' => chr utf8::unicode_to_native(0x92), ); # These are the aliases above that differ under :loose and :full matching # because the :full versions have blanks or hyphens in them. #my %loose_system_aliases = ( #); #my %deprecated_aliases; #$deprecated_aliases{'BELL'} = chr utf8::unicode_to_native(0x07) if $^V lt v5.17.0; #my %loose_deprecated_aliases = ( #); # These are special cased in :loose matching, differing only in a medial # hyphen my $HANGUL_JUNGSEONG_O_E_utf8 = chr 0x1180; my $HANGUL_JUNGSEONG_OE_utf8 = chr 0x116C; my $txt; # The table of official character names my %full_names_cache; # Holds already-looked-up names, so don't have to # re-look them up again. The previous versions of charnames had scoping # bugs. For example if we use script A in one scope and find and cache # what Z resolves to, we can't use that cache in a different scope that # uses script B instead of A, as Z might be an entirely different letter # there; or there might be different aliases in effect in different # scopes, or :short may be in effect or not effect in different scopes, # or various combinations thereof. This was solved in this version # mostly by moving things to %^H. But some things couldn't be moved # there. One of them was the cache of runtime looked-up names, in part # because %^H is read-only at runtime. I (khw) don't know why the cache # was run-time only in the previous versions: perhaps oversight; perhaps # that compile time looking doesn't happen in a loop so didn't think it # was worthwhile; perhaps not wanting to make the cache too large. But # I decided to make it compile time as well; this could easily be # changed. # Anyway, this hash is not scoped, and is added to at runtime. It # doesn't have scoping problems because the data in it is restricted to # official names, which are always invariant, and we only set it and # look at it at during :full lookups, so is unaffected by any other # scoped options. I put this in to maintain parity with the older # version. If desired, a %short_names cache could also be made, as well # as one for each script, say in %script_names_cache, with each key # being a hash for a script named in a 'use charnames' statement. I # decided not to do that for now, just because it's added complication, # and because I'm just trying to maintain parity, not extend it. # Like %full_names_cache, but for use when :loose is in effect. There needs # to be two caches because :loose may not be in effect for a scope, and a # loose name could inappropriately be returned when only exact matching is # called for. my %loose_names_cache; # Designed so that test decimal first, and then hex. Leading zeros # imply non-decimal, as do non-[0-9] my $decimal_qr = qr/^[1-9]\d*$/; # Returns the hex number in $1. my $hex_qr = qr/^(?:[Uu]\+|0[xX])?([[:xdigit:]]+)$/; sub croak { require Carp; goto &Carp::croak; } # croak sub carp { require Carp; goto &Carp::carp; } # carp sub alias (@) # Set up a single alias { my @errors; my $nbsp = chr utf8::unicode_to_native(0xA0); my $alias = ref $_[0] ? $_[0] : { @_ }; foreach my $name (sort keys %$alias) { # Sort only because it helps having # deterministic output for # t/lib/charnames/alias my $value = $alias->{$name}; next unless defined $value; # Omit if screwed up. # Is slightly slower to just after this statement see if it is # decimal, since we already know it is after having converted from # hex, but makes the code easier to maintain, and is called # infrequently, only at compile-time if ($value !~ $decimal_qr && $value =~ $hex_qr) { my $temp = CORE::hex $1; $temp = utf8::unicode_to_native($temp) if $value =~ /^[Uu]\+/; $value = $temp; } if ($value =~ $decimal_qr) { no warnings qw(non_unicode surrogate nonchar); # Allow any of these $^H{charnames_ord_aliases}{$name} = chr $value; # Use a canonical form. $^H{charnames_inverse_ords}{sprintf("%05X", $value)} = $name; } else { my $ok_portion = ""; $ok_portion = $1 if $name =~ / ^ ( \p{_Perl_Charname_Begin} \p{_Perl_Charname_Continue}* ) /x; # If the name was fully correct, the above should have matched all of # it. if (length $ok_portion < length $name) { my $first_bad = substr($name, length($ok_portion), 1); push @errors, "Invalid character in charnames alias definition; " . "marked by <-- HERE in '$ok_portion$first_bad<-- HERE " . substr($name, length($ok_portion) + 1) . "'"; } else { if ($name =~ / ( .* \s ) ( \s* ) $ /x) { push @errors, "charnames alias definitions may not contain " . "trailing white-space; marked by <-- HERE in " . "'$1 <-- HERE " . $2 . "'"; next; } # Use '+' instead of '*' in this regex, because any trailing # blanks have already been found if ($name =~ / ( .*? \s{2} ) ( .+ ) /x) { push @errors, "charnames alias definitions may not contain a " . "sequence of multiple spaces; marked by <-- HERE " . "in '$1 <-- HERE " . $2 . "'"; next; } $^H{charnames_name_aliases}{$name} = $value; } } } # We find and output all errors from this :alias definition, rather than # failing on the first one, so fewer runs are needed to get it to compile if (@errors) { croak join "\n", @errors; } return; } # alias sub not_legal_use_bytes_msg { my ($name, $utf8) = @_; my $return; if (length($utf8) == 1) { $return = sprintf("Character 0x%04x with name '%s' is", ord $utf8, $name); } else { $return = sprintf("String with name '%s' (and ordinals %s) contains character(s)", $name, join(" ", map { sprintf "0x%04X", ord $_ } split(//, $utf8))); } return $return . " above 0xFF with 'use bytes' in effect"; } sub alias_file ($) # Reads a file containing alias definitions { require File::Spec; my ($arg, $file) = @_; if (-f $arg && File::Spec->file_name_is_absolute ($arg)) { $file = $arg; } elsif ($arg =~ m/ ^ \p{_Perl_IDStart} \p{_Perl_IDCont}* $/x) { $file = "unicore/${arg}_alias.pl"; } else { croak "Charnames alias file names can only have identifier characters"; } if (my @alias = do $file) { @alias == 1 && !defined $alias[0] and croak "$file cannot be used as alias file for charnames"; @alias % 2 and croak "$file did not return a (valid) list of alias pairs"; alias (@alias); return (1); } 0; } # alias_file # For use when don't import anything. This structure must be kept in # sync with the one that import() fills up. my %dummy_H = ( charnames_stringified_names => "", charnames_stringified_ords => "", charnames_scripts => "", charnames_full => 1, charnames_loose => 0, charnames_short => 0, ); sub lookup_name ($$$) { my ($name, $wants_ord, $runtime) = @_; # Lookup the name or sequence $name in the tables. If $wants_ord is false, # returns the string equivalent of $name; if true, returns the ordinal value # instead, but in this case $name must not be a sequence; otherwise undef is # returned and a warning raised. $runtime is 0 if compiletime, otherwise # gives the number of stack frames to go back to get the application caller # info. # If $name is not found, returns undef in runtime with no warning; and in # compiletime, the Unicode replacement character, with a warning. # It looks first in the aliases, then in the large table of official Unicode # names. my $result; # The string result my $save_input; if ($runtime) { my $hints_ref = (caller($runtime))[10]; # If we didn't import anything (which happens with 'use charnames ()', # substitute a dummy structure. $hints_ref = \%dummy_H if ! defined $hints_ref || (! defined $hints_ref->{charnames_full} && ! defined $hints_ref->{charnames_loose}); # At runtime, but currently not at compile time, %^H gets # stringified, so un-stringify back to the original data structures. # These get thrown away by perl before the next invocation # Also fill in the hash with the non-stringified data. # N.B. New fields must be also added to %dummy_H %{$^H{charnames_name_aliases}} = split ',', $hints_ref->{charnames_stringified_names}; %{$^H{charnames_ord_aliases}} = split ',', $hints_ref->{charnames_stringified_ords}; $^H{charnames_scripts} = $hints_ref->{charnames_scripts}; $^H{charnames_full} = $hints_ref->{charnames_full}; $^H{charnames_loose} = $hints_ref->{charnames_loose}; $^H{charnames_short} = $hints_ref->{charnames_short}; } my $loose = $^H{charnames_loose}; my $lookup_name; # Input name suitably modified for grepping for in the # table # User alias should be checked first or else can't override ours, and if we # were to add any, could conflict with theirs. if (exists $^H{charnames_ord_aliases}{$name}) { $result = $^H{charnames_ord_aliases}{$name}; } elsif (exists $^H{charnames_name_aliases}{$name}) { $name = $^H{charnames_name_aliases}{$name}; $save_input = $lookup_name = $name; # Cache the result for any error # message # The aliases are documented to not match loosely, so change loose match # into full. if ($loose) { $loose = 0; $^H{charnames_full} = 1; } } else { # Here, not a user alias. That means that loose matching may be in # effect; will have to modify the input name. $lookup_name = $name; if ($loose) { $lookup_name = uc $lookup_name; # Squeeze out all underscores $lookup_name =~ s/_//g; # Remove all medial hyphens $lookup_name =~ s/ (?<= \S ) - (?= \S )//gx; # Squeeze out all spaces $lookup_name =~ s/\s//g; } # Here, $lookup_name has been modified as necessary for looking in the # hashes. Check the system alias files next. Most of these aliases are # the same for both strict and loose matching. To save space, the ones # which differ are in their own separate hash, which is checked if loose # matching is selected and the regular match fails. To save time, the # loose hashes could be expanded to include all aliases, and there would # only have to be one check. But if someone specifies :loose, they are # interested in convenience over speed, and the time for this second check # is miniscule compared to the rest of the routine. if (exists $system_aliases{$lookup_name}) { $result = $system_aliases{$lookup_name}; } # There are currently no entries in this hash, so don't waste time looking # for them. But the code is retained for the unlikely possibility that # some will be added in the future. # elsif ($loose && exists $loose_system_aliases{$lookup_name}) { # $result = $loose_system_aliases{$lookup_name}; # } # if (exists $deprecated_aliases{$lookup_name}) { # require warnings; # warnings::warnif('deprecated', # "Unicode character name \"$name\" is deprecated, use \"" # . viacode(ord $deprecated_aliases{$lookup_name}) # . "\" instead"); # $result = $deprecated_aliases{$lookup_name}; # } # There are currently no entries in this hash, so don't waste time looking # for them. But the code is retained for the unlikely possibility that # some will be added in the future. # elsif ($loose && exists $loose_deprecated_aliases{$lookup_name}) { # require warnings; # warnings::warnif('deprecated', # "Unicode character name \"$name\" is deprecated, use \"" # . viacode(ord $loose_deprecated_aliases{$lookup_name}) # . "\" instead"); # $result = $loose_deprecated_aliases{$lookup_name}; # } } my @off; # Offsets into table of pattern match begin and end # If haven't found it yet... if (! defined $result) { # See if has looked this input up earlier. if (! $loose && $^H{charnames_full} && exists $full_names_cache{$name}) { $result = $full_names_cache{$name}; } elsif ($loose && exists $loose_names_cache{$name}) { $result = $loose_names_cache{$name}; } else { # Here, must do a look-up # If full or loose matching succeeded, points to where to cache the # result my $cache_ref; ## Suck in the code/name list as a big string. ## Lines look like: ## "00052\tLATIN CAPITAL LETTER R\n" # or # "0052 0303\tLATIN CAPITAL LETTER R WITH TILDE\n" $txt = do "unicore/Name.pl" unless $txt; ## @off will hold the index into the code/name string of the start and ## end of the name as we find it. ## If :loose, look for a loose match; if :full, look for the name ## exactly # First, see if the name is one which is algorithmically determinable. # The subroutine is included in Name.pl. The table contained in # $txt doesn't contain these. Experiments show that checking # for these before checking for the regular names has no # noticeable impact on performance for the regular names, but # the other way around slows down finding these immensely. # Algorithmically determinables are not placed in the cache because # that uses up memory, and finding these again is fast. if (($loose || $^H{charnames_full}) && (defined (my $ord = charnames::name_to_code_point_special($lookup_name, $loose)))) { $result = chr $ord; } else { # Not algorithmically determinable; look up in the table. The name # will be turned into a regex, so quote any meta characters. $lookup_name = quotemeta $lookup_name; if ($loose) { # For loose matches, $lookup_name has already squeezed out the # non-essential characters. We have to add in code to make the # squeezed version match the non-squeezed equivalent in the table. # The only remaining hyphens are ones that start or end a word in # the original. They have been quoted in $lookup_name so they look # like "\-". Change all other characters except the backslash # quotes for any metacharacters, and the final character, so that # e.g., COLON gets transformed into: /C[- ]?O[- ]?L[- ]?O[- ]?N/ $lookup_name =~ s/ (?! \\ -) # Don't do this to the \- sequence ( [^-\\] ) # Nor the "-" within that sequence, # nor the "\" that quotes metachars, # but otherwise put the char into $1 (?=.) # And don't do it for the final char /$1\[- \]?/gx; # And add an optional blank or # '-' after each $1 char # Those remaining hyphens were originally at the beginning or end of # a word, so they can match either a blank before or after, but not # both. (Keep in mind that they have been quoted, so are a '\-' # sequence) $lookup_name =~ s/\\ -/(?:- | -)/xg; } # Do the lookup in the full table if asked for, and if succeeds # save the offsets and set where to cache the result. if (($loose || $^H{charnames_full}) && $txt =~ /\t$lookup_name$/m) { @off = ($-[0] + 1, $+[0]); # The 1 is for the tab $cache_ref = ($loose) ? \%loose_names_cache : \%full_names_cache; } else { # Here, didn't look for, or didn't find the name. # If :short is allowed, see if input is like "greek:Sigma". # Keep in mind that $lookup_name has had the metas quoted. my $scripts_trie = ""; my $name_has_uppercase; if (($^H{charnames_short}) && $lookup_name =~ /^ (?: \\ \s)* # Quoted space (.+?) # $1 = the script (?: \\ \s)* \\ : # Quoted colon (?: \\ \s)* (.+?) # $2 = the name (?: \\ \s)* $ /xs) { # Even in non-loose matching, the script traditionally has been # case insensitive $scripts_trie = "\U$1"; $lookup_name = $2; # Use original name to find its input casing, but ignore the # script part of that to make the determination. $save_input = $name if ! defined $save_input; $name =~ s/.*?://; $name_has_uppercase = $name =~ /[[:upper:]]/; } else { # Otherwise look in allowed scripts $scripts_trie = $^H{charnames_scripts}; # Use original name to find its input casing $name_has_uppercase = $name =~ /[[:upper:]]/; } my $case = $name_has_uppercase ? "CAPITAL" : "SMALL"; return if (! $scripts_trie || $txt !~ /\t (?: $scripts_trie ) \ (?:$case\ )? LETTER \ \U$lookup_name $/xm); # Here have found the input name in the table. @off = ($-[0] + 1, $+[0]); # The 1 is for the tab } # Here, the input name has been found; we haven't set up the output, # but we know where in the string # the name starts. The string is set up so that for single characters # (and not named sequences), the name is preceded immediately by a # tab and 5 hex digits for its code, with a \n before those. Named # sequences won't have the 7th preceding character be a \n. # (Actually, for the very first entry in the table this isn't strictly # true: subtracting 7 will yield -1, and the substr below will # therefore yield the very last character in the table, which should # also be a \n, so the statement works anyway.) if (substr($txt, $off[0] - 7, 1) eq "\n") { $result = chr CORE::hex substr($txt, $off[0] - 6, 5); # Handle the single loose matching special case, in which two names # differ only by a single medial hyphen. If the original had a # hyphen (or more) in the right place, then it is that one. $result = $HANGUL_JUNGSEONG_O_E_utf8 if $loose && $result eq $HANGUL_JUNGSEONG_OE_utf8 && $name =~ m/O \s* - [-\s]* E/ix; # Note that this wouldn't work if there were a 2nd # OE in the name } else { # Here, is a named sequence. Need to go looking for the beginning, # which is just after the \n from the previous entry in the table. # The +1 skips past that newline, or, if the rindex() fails, to put # us to an offset of zero. my $charstart = rindex($txt, "\n", $off[0] - 7) + 1; $result = pack("W*", map { CORE::hex } split " ", substr($txt, $charstart, $off[0] - $charstart - 1)); } } # Cache the input so as to not have to search the large table # again, but only if it came from the one search that we cache. # (Haven't bothered with the pain of sorting out scoping issues for the # scripts searches.) $cache_ref->{$name} = $result if defined $cache_ref; } } # Here, have the result character. If the return is to be an ord, must be # any single character. if ($wants_ord) { return ord($result) if length $result == 1; } elsif (! utf8::is_utf8($result)) { # Here isn't UTF-8. That's OK if it is all ASCII, or we are being called # at compile time where we know we can guarantee that Unicode rules are # correctly imposed on the result, or under 'bytes' where we don't want # those rules. But otherwise we have to make it UTF8 to guarantee Unicode # rules on the returned string. return $result if ! $runtime || (caller $runtime)[8] & $bytes::hint_bits || $result !~ /[[:^ascii:]]/; utf8::upgrade($result); return $result; } else { # Here, wants string output. If utf8 is acceptable, just return what # we've got; otherwise attempt to convert it to non-utf8 and return that. my $in_bytes = ($runtime) ? (caller $runtime)[8] & $bytes::hint_bits : $^H & $bytes::hint_bits; return $result if (! $in_bytes || utf8::downgrade($result, 1)) # The 1 arg # means don't die on failure } # Here, there is an error: either there are too many characters, or the # result string needs to be non-utf8, and at least one character requires # utf8. Prefer any official name over the input one for the error message. if (@off) { $name = substr($txt, $off[0], $off[1] - $off[0]) if @off; } else { $name = (defined $save_input) ? $save_input : $_[0]; } if ($wants_ord) { # Only way to get here in this case is if result too long. Message # assumes that our only caller that requires single char result is # vianame. carp "charnames::vianame() doesn't handle named sequences ($name). Use charnames::string_vianame() instead"; return; } # Only other possible failure here is from use bytes. if ($runtime) { carp not_legal_use_bytes_msg($name, $result); return; } else { croak not_legal_use_bytes_msg($name, $result); } } # lookup_name sub charnames { # For \N{...}. Looks up the character name and returns the string # representation of it. # The first 0 arg means wants a string returned; the second that we are in # compile time return lookup_name($_[0], 0, 0); } sub import { shift; ## ignore class name if (not @_) { carp("'use charnames' needs explicit imports list"); } $^H{charnames} = \&charnames ; $^H{charnames_ord_aliases} = {}; $^H{charnames_name_aliases} = {}; $^H{charnames_inverse_ords} = {}; # New fields must be added to %dummy_H, and the code in lookup_name() # that copies fields from the runtime structure ## ## fill %h keys with our @_ args. ## my ($promote, %h, @args) = (0); while (my $arg = shift) { if ($arg eq ":alias") { @_ or croak ":alias needs an argument in charnames"; my $alias = shift; if (ref $alias) { ref $alias eq "HASH" or croak "Only HASH reference supported as argument to :alias"; alias ($alias); $promote = 1; next; } if ($alias =~ m{:(\w+)$}) { $1 eq "full" || $1 eq "loose" || $1 eq "short" and croak ":alias cannot use existing pragma :$1 (reversed order?)"; alias_file ($1) and $promote = 1; next; } alias_file ($alias) and $promote = 1; next; } if (substr($arg, 0, 1) eq ':' and ! ($arg eq ":full" || $arg eq ":short" || $arg eq ":loose")) { warn "unsupported special '$arg' in charnames"; next; } push @args, $arg; } @args == 0 && $promote and @args = (":full"); @h{@args} = (1) x @args; # Don't leave these undefined as are tested for in lookup_names $^H{charnames_full} = delete $h{':full'} || 0; $^H{charnames_loose} = delete $h{':loose'} || 0; $^H{charnames_short} = delete $h{':short'} || 0; my @scripts = map { uc quotemeta } keys %h; ## ## If utf8? warnings are enabled, and some scripts were given, ## see if at least we can find one letter from each script. ## if (warnings::enabled('utf8') && @scripts) { $txt = do "unicore/Name.pl" unless $txt; for my $script (@scripts) { if (not $txt =~ m/\t$script (?:CAPITAL |SMALL )?LETTER /) { warnings::warn('utf8', "No such script: '$script'"); $script = quotemeta $script; # Escape it, for use in the re. } } } # %^H gets stringified, so serialize it ourselves so can extract the # real data back later. $^H{charnames_stringified_ords} = join ",", %{$^H{charnames_ord_aliases}}; $^H{charnames_stringified_names} = join ",", %{$^H{charnames_name_aliases}}; $^H{charnames_stringified_inverse_ords} = join ",", %{$^H{charnames_inverse_ords}}; # Modify the input script names for loose name matching if that is also # specified, similar to the way the base character name is prepared. They # don't (currently, and hopefully never will) have dashes. These go into a # regex, and have already been uppercased and quotemeta'd. Squeeze out all # input underscores, blanks, and dashes. Then convert so will match a blank # between any characters. if ($^H{charnames_loose}) { for (my $i = 0; $i < @scripts; $i++) { $scripts[$i] =~ s/[_ -]//g; $scripts[$i] =~ s/ ( [^\\] ) (?= . ) /$1\\ ?/gx; } } $^H{charnames_scripts} = join "|", @scripts; # Stringifiy them as a trie } # import # Cache of already looked-up values. This is set to only contain # official values, and user aliases can't override them, so scoping is # not an issue. my %viacode; my $no_name_code_points_re = join "|", map { sprintf("%05X", utf8::unicode_to_native($_)) } 0x80, 0x81, 0x84, 0x99; $no_name_code_points_re = qr/$no_name_code_points_re/; sub viacode { # Returns the name of the code point argument if (@_ != 1) { carp "charnames::viacode() expects one argument"; return; } my $arg = shift; # This is derived from Unicode::UCD, where it is nearly the same as the # function _getcode(), but here it makes sure that even a hex argument # has the proper number of leading zeros, which is critical in # matching against $txt below # Must check if decimal first; see comments at that definition my $hex; if ($arg =~ $decimal_qr) { $hex = sprintf "%05X", $arg; } elsif ($arg =~ $hex_qr) { $hex = CORE::hex $1; $hex = utf8::unicode_to_native($hex) if $arg =~ /^[Uu]\+/; # Below is the line that differs from the _getcode() source $hex = sprintf "%05X", $hex; } else { carp("unexpected arg \"$arg\" to charnames::viacode()"); return; } return $viacode{$hex} if exists $viacode{$hex}; my $return; # If the code point is above the max in the table, there's no point # looking through it. Checking the length first is slightly faster if (length($hex) <= 5 || CORE::hex($hex) <= 0x10FFFF) { $txt = do "unicore/Name.pl" unless $txt; # See if the name is algorithmically determinable. my $algorithmic = charnames::code_point_to_name_special(CORE::hex $hex); if (defined $algorithmic) { $viacode{$hex} = $algorithmic; return $algorithmic; } # Return the official name, if exists. It's unclear to me (khw) at # this juncture if it is better to return a user-defined override, so # leaving it as is for now. if ($txt =~ m/^$hex\t/m) { # The name starts with the next character and goes up to the # next new-line. Using capturing parentheses above instead of # @+ more than doubles the execution time in Perl 5.13 $return = substr($txt, $+[0], index($txt, "\n", $+[0]) - $+[0]); # If not one of these 4 code points, return what we've found. if ($hex !~ / ^ $no_name_code_points_re $ /x) { $viacode{$hex} = $return; return $return; } # For backwards compatibility, we don't return the official name of # the 4 code points if there are user-defined aliases for them -- so # continue looking. } } # See if there is a user name for it, before giving up completely. # First get the scoped aliases, give up if have none. my $H_ref = (caller(1))[10]; return if ! defined $return && (! defined $H_ref || ! exists $H_ref->{charnames_stringified_inverse_ords}); my %code_point_aliases; if (defined $H_ref->{charnames_stringified_inverse_ords}) { %code_point_aliases = split ',', $H_ref->{charnames_stringified_inverse_ords}; return $code_point_aliases{$hex} if exists $code_point_aliases{$hex}; } # Here there is no user-defined alias, return any official one. return $return if defined $return; if (CORE::hex($hex) > 0x10FFFF && warnings::enabled('non_unicode')) { carp "Unicode characters only allocated up to U+10FFFF (you asked for U+$hex)"; } return; } # viacode 1; # ex: set ts=8 sts=2 sw=2 et: package open; use warnings; our $VERSION = '1.11'; require 5.008001; # for PerlIO::get_layers() my $locale_encoding; sub _get_encname { return ($1, Encode::resolve_alias($1)) if $_[0] =~ /^:?encoding\((.+)\)$/; return; } sub croak { require Carp; goto &Carp::croak; } sub _drop_oldenc { # If by the time we arrive here there already is at the top of the # perlio layer stack an encoding identical to what we would like # to push via this open pragma, we will pop away the old encoding # (+utf8) so that we can push ourselves in place (this is easier # than ignoring pushing ourselves because of the way how ${^OPEN} # works). So we are looking for something like # # stdio encoding(xxx) utf8 # # in the existing layer stack, and in the new stack chunk for # # :encoding(xxx) # # If we find a match, we pop the old stack (once, since # the utf8 is just a flag on the encoding layer) my ($h, @new) = @_; return unless @new >= 1 && $new[-1] =~ /^:encoding\(.+\)$/; my @old = PerlIO::get_layers($h); return unless @old >= 3 && $old[-1] eq 'utf8' && $old[-2] =~ /^encoding\(.+\)$/; require Encode; my ($loname, $lcname) = _get_encname($old[-2]); unless (defined $lcname) { # Should we trust get_layers()? croak("open: Unknown encoding '$loname'"); } my ($voname, $vcname) = _get_encname($new[-1]); unless (defined $vcname) { croak("open: Unknown encoding '$voname'"); } if ($lcname eq $vcname) { binmode($h, ":pop"); # utf8 is part of the encoding layer } } sub import { my ($class,@args) = @_; croak("open: needs explicit list of PerlIO layers") unless @args; my $std; my ($in,$out) = split(/\0/,(${^OPEN} || "\0"), -1); while (@args) { my $type = shift(@args); my $dscp; if ($type =~ /^:?(utf8|locale|encoding\(.+\))$/) { $type = 'IO'; $dscp = ":$1"; } elsif ($type eq ':std') { $std = 1; next; } else { $dscp = shift(@args) || ''; } my @val; foreach my $layer (split(/\s+/,$dscp)) { $layer =~ s/^://; if ($layer eq 'locale') { require Encode; require encoding; $locale_encoding = encoding::_get_locale_encoding() unless defined $locale_encoding; (warnings::warnif("layer", "Cannot figure out an encoding to use"), last) unless defined $locale_encoding; $layer = "encoding($locale_encoding)"; $std = 1; } else { my $target = $layer; # the layer name itself $target =~ s/^(\w+)\(.+\)$/$1/; # strip parameters unless(PerlIO::Layer::->find($target,1)) { warnings::warnif("layer", "Unknown PerlIO layer '$target'"); } } push(@val,":$layer"); if ($layer =~ /^(crlf|raw)$/) { $^H{"open_$type"} = $layer; } } if ($type eq 'IN') { _drop_oldenc(*STDIN, @val) if $std; $in = join(' ', @val); } elsif ($type eq 'OUT') { if ($std) { _drop_oldenc(*STDOUT, @val); _drop_oldenc(*STDERR, @val); } $out = join(' ', @val); } elsif ($type eq 'IO') { if ($std) { _drop_oldenc(*STDIN, @val); _drop_oldenc(*STDOUT, @val); _drop_oldenc(*STDERR, @val); } $in = $out = join(' ', @val); } else { croak "Unknown PerlIO layer class '$type' (need IN, OUT or IO)"; } } ${^OPEN} = join("\0", $in, $out); if ($std) { if ($in) { if ($in =~ /:utf8\b/) { binmode(STDIN, ":utf8"); } elsif ($in =~ /(\w+\(.+\))/) { binmode(STDIN, ":$1"); } } if ($out) { if ($out =~ /:utf8\b/) { binmode(STDOUT, ":utf8"); binmode(STDERR, ":utf8"); } elsif ($out =~ /(\w+\(.+\))/) { binmode(STDOUT, ":$1"); binmode(STDERR, ":$1"); } } } } 1; __END__ package UNIVERSAL; our $VERSION = '1.13'; # UNIVERSAL should not contain any extra subs/methods beyond those # that it exists to define. The existence of import() below is a historical # accident that can't be fixed without breaking code. # Make sure that even though the import method is called, it doesn't do # anything unless called on UNIVERSAL. sub import { return unless $_[0] eq __PACKAGE__; return unless @_ > 1; require Carp; Carp::croak("UNIVERSAL does not export anything"); } 1; __END__ package DB; use strict; use Cwd (); my $_initial_cwd; BEGIN {eval 'use IO::Handle'}; # Needed for flush only? breaks under miniperl BEGIN { require feature; $^V =~ /^v(\d+\.\d+)/; feature->import(":$1"); $_initial_cwd = Cwd::getcwd(); } # Debugger for Perl 5.00x; perl5db.pl patch level: use vars qw($VERSION $header); # bump to X.XX in blead, only use X.XX_XX in maint $VERSION = '1.53'; $header = "perl5db.pl version $VERSION"; ############################################## Begin lexical danger zone # 'my' variables used here could leak into (that is, be visible in) # the context that the code being evaluated is executing in. This means that # the code could modify the debugger's variables. # # Fiddling with the debugger's context could be Bad. We insulate things as # much as we can. use vars qw( @args %break_on_load $CommandSet $CreateTTY $DBGR @dbline $dbline %dbline $dieLevel $filename $histfile $histsize $IN $inhibit_exit @ini_INC $ini_warn $maxtrace $od @options $osingle $otrace $pager $post %postponed $prc $pre $pretype $psh @RememberOnROptions $remoteport @res $rl @saved $signalLevel $sub $term $usercontext $warnLevel ); our ( @cmdfhs, $evalarg, $frame, $hist, $ImmediateStop, $line, $onetimeDump, $onetimedumpDepth, %option, $OUT, $packname, $signal, $single, $start, %sub, $subname, $trace, $window, ); # Used to save @ARGV and extract any debugger-related flags. use vars qw(@ARGS); # Used to prevent multiple entries to diesignal() # (if for instance diesignal() itself dies) use vars qw($panic); # Used to prevent the debugger from running nonstop # after a restart our ($second_time); sub _calc_usercontext { my ($package) = @_; # Cancel strict completely for the evaluated code, so the code # the user evaluates won't be affected by it. (Shlomi Fish) return 'no strict; ($@, $!, $^E, $,, $/, $\, $^W) = @DB::saved;' . "package $package;"; # this won't let them modify, alas } sub eval { # 'my' would make it visible from user code # but so does local! --tchrist # Remember: this localizes @DB::res, not @main::res. local @res; { # Try to keep the user code from messing with us. Save these so that # even if the eval'ed code changes them, we can put them back again. # Needed because the user could refer directly to the debugger's # package globals (and any 'my' variables in this containing scope) # inside the eval(), and we want to try to stay safe. local $otrace = $trace; local $osingle = $single; local $od = $^D; # Untaint the incoming eval() argument. { ($evalarg) = $evalarg =~ /(.*)/s; } # $usercontext built in DB::DB near the comment # "set up the context for DB::eval ..." # Evaluate and save any results. @res = eval "$usercontext $evalarg;\n"; # '\n' for nice recursive debug # Restore those old values. $trace = $otrace; $single = $osingle; $^D = $od; } # Save the current value of $@, and preserve it in the debugger's copy # of the saved precious globals. my $at = $@; # Since we're only saving $@, we only have to localize the array element # that it will be stored in. local $saved[0]; # Preserve the old value of $@ eval { &DB::save }; # Now see whether we need to report an error back to the user. if ($at) { local $\ = ''; print $OUT $at; } # Display as required by the caller. $onetimeDump and $onetimedumpDepth # are package globals. elsif ($onetimeDump) { if ( $onetimeDump eq 'dump' ) { local $option{dumpDepth} = $onetimedumpDepth if defined $onetimedumpDepth; dumpit( $OUT, \@res ); } elsif ( $onetimeDump eq 'methods' ) { methods( $res[0] ); } } ## end elsif ($onetimeDump) @res; } ## end sub eval ############################################## End lexical danger zone # After this point it is safe to introduce lexicals. # The code being debugged will be executing in its own context, and # can't see the inside of the debugger. # # However, one should not overdo it: leave as much control from outside as # possible. If you make something a lexical, it's not going to be addressable # from outside the debugger even if you know its name. # This file is automatically included if you do perl -d. # It's probably not useful to include this yourself. # # Before venturing further into these twisty passages, it is # wise to read the perldebguts man page or risk the ire of dragons. # # (It should be noted that perldebguts will tell you a lot about # the underlying mechanics of how the debugger interfaces into the # Perl interpreter, but not a lot about the debugger itself. The new # comments in this code try to address this problem.) # Note that no subroutine call is possible until &DB::sub is defined # (for subroutines defined outside of the package DB). In fact the same is # true if $deep is not defined. # Enhanced by ilya@math.ohio-state.edu (Ilya Zakharevich) # modified Perl debugger, to be run from Emacs in perldb-mode # Ray Lischner (uunet!mntgfx!lisch) as of 5 Nov 1990 # Johan Vromans -- upgrade to 4.0 pl 10 # Ilya Zakharevich -- patches after 5.001 (and some before ;-) ######################################################################## # Needed for the statement after exec(): # # This BEGIN block is simply used to switch off warnings during debugger # compilation. Probably it would be better practice to fix the warnings, # but this is how it's done at the moment. BEGIN { $ini_warn = $^W; $^W = 0; } # Switch compilation warnings off until another BEGIN. local ($^W) = 0; # Switch run-time warnings off during init. BEGIN { # ensure we can share our non-threaded variables or no-op if ($ENV{PERL5DB_THREADED}) { require threads; require threads::shared; import threads::shared qw(share); $DBGR; share(\$DBGR); lock($DBGR); print "Threads support enabled\n"; } else { *lock = sub(*) {}; *share = sub(\[$@%]) {}; } } # These variables control the execution of 'dumpvar.pl'. { package dumpvar; use vars qw( $hashDepth $arrayDepth $dumpDBFiles $dumpPackages $quoteHighBit $printUndef $globPrint $usageOnly ); } # used to control die() reporting in diesignal() { package Carp; use vars qw($CarpLevel); } # without threads, $filename is not defined until DB::DB is called share($main::{'_<'.$filename}) if defined $filename; # Command-line + PERLLIB: # Save the contents of @INC before they are modified elsewhere. @ini_INC = @INC; # This was an attempt to clear out the previous values of various # trapped errors. Apparently it didn't help. XXX More info needed! # $prevwarn = $prevdie = $prevbus = $prevsegv = ''; # Does not help?! # We set these variables to safe values. We don't want to blindly turn # off warnings, because other packages may still want them. $trace = $signal = $single = 0; # Uninitialized warning suppression # (local $^W cannot help - other packages!). # Default to not exiting when program finishes; print the return # value when the 'r' command is used to return from a subroutine. $inhibit_exit = $option{PrintRet} = 1; use vars qw($trace_to_depth); # Default to 1E9 so it won't be limited to a certain recursion depth. $trace_to_depth = 1E9; @options = qw( CommandSet HistFile HistSize hashDepth arrayDepth dumpDepth DumpDBFiles DumpPackages DumpReused compactDump veryCompact quote HighBit undefPrint globPrint PrintRet UsageOnly frame AutoTrace TTY noTTY ReadLine NonStop LineInfo maxTraceLen recallCommand ShellBang pager tkRunning ornaments signalLevel warnLevel dieLevel inhibit_exit ImmediateStop bareStringify CreateTTY RemotePort windowSize DollarCaretP ); @RememberOnROptions = qw(DollarCaretP); use vars qw(%optionVars); %optionVars = ( hashDepth => \$dumpvar::hashDepth, arrayDepth => \$dumpvar::arrayDepth, CommandSet => \$CommandSet, DumpDBFiles => \$dumpvar::dumpDBFiles, DumpPackages => \$dumpvar::dumpPackages, DumpReused => \$dumpvar::dumpReused, HighBit => \$dumpvar::quoteHighBit, undefPrint => \$dumpvar::printUndef, globPrint => \$dumpvar::globPrint, UsageOnly => \$dumpvar::usageOnly, CreateTTY => \$CreateTTY, bareStringify => \$dumpvar::bareStringify, frame => \$frame, AutoTrace => \$trace, inhibit_exit => \$inhibit_exit, maxTraceLen => \$maxtrace, ImmediateStop => \$ImmediateStop, RemotePort => \$remoteport, windowSize => \$window, HistFile => \$histfile, HistSize => \$histsize, ); use vars qw(%optionAction); %optionAction = ( compactDump => \&dumpvar::compactDump, veryCompact => \&dumpvar::veryCompact, quote => \&dumpvar::quote, TTY => \&TTY, noTTY => \&noTTY, ReadLine => \&ReadLine, NonStop => \&NonStop, LineInfo => \&LineInfo, recallCommand => \&recallCommand, ShellBang => \&shellBang, pager => \&pager, signalLevel => \&signalLevel, warnLevel => \&warnLevel, dieLevel => \&dieLevel, tkRunning => \&tkRunning, ornaments => \&ornaments, RemotePort => \&RemotePort, DollarCaretP => \&DollarCaretP, ); # Note that this list is not complete: several options not listed here # actually require that dumpvar.pl be loaded for them to work, but are # not in the table. A subsequent patch will correct this problem; for # the moment, we're just recommenting, and we are NOT going to change # function. use vars qw(%optionRequire); %optionRequire = ( compactDump => 'dumpvar.pl', veryCompact => 'dumpvar.pl', quote => 'dumpvar.pl', ); # These guys may be defined in $ENV{PERL5DB} : $rl = 1 unless defined $rl; $warnLevel = 1 unless defined $warnLevel; $dieLevel = 1 unless defined $dieLevel; $signalLevel = 1 unless defined $signalLevel; $pre = [] unless defined $pre; $post = [] unless defined $post; $pretype = [] unless defined $pretype; $CreateTTY = 3 unless defined $CreateTTY; $CommandSet = '580' unless defined $CommandSet; share($rl); share($warnLevel); share($dieLevel); share($signalLevel); share($pre); share($post); share($pretype); share($rl); share($CreateTTY); share($CommandSet); warnLevel($warnLevel); dieLevel($dieLevel); signalLevel($signalLevel); # This routine makes sure $pager is set up so that '|' can use it. pager( # If PAGER is defined in the environment, use it. defined $ENV{PAGER} ? $ENV{PAGER} # If not, see if Config.pm defines it. : eval { require Config } && defined $Config::Config{pager} ? $Config::Config{pager} # If not, fall back to 'more'. : 'more' ) unless defined $pager; setman(); # Set up defaults for command recall and shell escape (note: # these currently don't work in linemode debugging). recallCommand("!") unless defined $prc; shellBang("!") unless defined $psh; sethelp(); # If we didn't get a default for the length of eval/stack trace args, # set it here. $maxtrace = 400 unless defined $maxtrace; # Save the current contents of the environment; we're about to # much with it. We'll need this if we have to restart. use vars qw($ini_pids); $ini_pids = $ENV{PERLDB_PIDS}; use vars qw ($pids $term_pid); if ( defined $ENV{PERLDB_PIDS} ) { # We're a child. Make us a label out of the current PID structure # recorded in PERLDB_PIDS plus our (new) PID. Mark us as not having # a term yet so the parent will give us one later via resetterm(). my $env_pids = $ENV{PERLDB_PIDS}; $pids = "[$env_pids]"; # Unless we are on OpenVMS, all programs under the DCL shell run under # the same PID. if (($^O eq 'VMS') && ($env_pids =~ /\b$$\b/)) { $term_pid = $$; } else { $ENV{PERLDB_PIDS} .= "->$$"; $term_pid = -1; } } ## end if (defined $ENV{PERLDB_PIDS... else { # We're the parent PID. Initialize PERLDB_PID in case we end up with a # child debugger, and mark us as the parent, so we'll know to set up # more TTY's is we have to. $ENV{PERLDB_PIDS} = "$$"; $pids = "[pid=$$]"; $term_pid = $$; } use vars qw($pidprompt); $pidprompt = ''; # Sets up $emacs as a synonym for $slave_editor. our ($slave_editor); *emacs = $slave_editor if $slave_editor; # May be used in afterinit()... # As noted, this test really doesn't check accurately that the debugger # is running at a terminal or not. use vars qw($rcfile); { my $dev_tty = (($^O eq 'VMS') ? 'TT:' : '/dev/tty'); # this is the wrong metric! $rcfile = ((-e $dev_tty) ? ".perldb" : "perldb.ini"); } # This wraps a safety test around "do" to read and evaluate the init file. # # This isn't really safe, because there's a race # between checking and opening. The solution is to # open and fstat the handle, but then you have to read and # eval the contents. But then the silly thing gets # your lexical scope, which is unfortunate at best. sub safe_do { my $file = shift; # Just exactly what part of the word "CORE::" don't you understand? local $SIG{__WARN__}; local $SIG{__DIE__}; unless ( is_safe_file($file) ) { CORE::warn < $b} keys(%pf); my @enabled_statuses = get_list("PERLDB_FILE_ENABLED_$file_idx"); for my $line_idx (0 .. $#lines) { _set_breakpoint_enabled_status( $filename, $lines[$line_idx], ($enabled_statuses[$line_idx] ? 1 : ''), ); } } return; } sub _restore_options_after_restart { my %options_map = get_list("PERLDB_OPT"); while ( my ( $opt, $val ) = each %options_map ) { $val =~ s/[\\\']/\\$1/g; parse_options("$opt'$val'"); } return; } sub _restore_globals_after_restart { # restore original @INC @INC = get_list("PERLDB_INC"); @ini_INC = @INC; # return pre/postprompt actions and typeahead buffer $pretype = [ get_list("PERLDB_PRETYPE") ]; $pre = [ get_list("PERLDB_PRE") ]; $post = [ get_list("PERLDB_POST") ]; @typeahead = get_list( "PERLDB_TYPEAHEAD", @typeahead ); return; } if ( exists $ENV{PERLDB_RESTART} ) { # We're restarting, so we don't need the flag that says to restart anymore. delete $ENV{PERLDB_RESTART}; # $restart = 1; _restore_shared_globals_after_restart(); _restore_breakpoints_and_actions(); # restore options _restore_options_after_restart(); _restore_globals_after_restart(); } ## end if (exists $ENV{PERLDB_RESTART... use vars qw($notty $console $tty $LINEINFO); use vars qw($lineinfo $doccmd); our ($runnonstop); # Local autoflush to avoid rt#116769, # as calling IO::File methods causes an unresolvable loop # that results in debugger failure. sub _autoflush { my $o = select($_[0]); $|++; select($o); } if ($notty) { $runnonstop = 1; share($runnonstop); } else { # Is Perl being run from a slave editor or graphical debugger? # If so, don't use readline, and set $slave_editor = 1. if ($slave_editor = ( @main::ARGV && ( $main::ARGV[0] eq '-emacs' ) )) { $rl = 0; shift(@main::ARGV); } #require Term::ReadLine; if ( $^O eq 'cygwin' ) { # /dev/tty is binary. use stdin for textmode undef $console; } elsif ( $^O eq 'dos' or -e "con" or $^O eq 'MSWin32' ) { $console = "con"; } elsif ( $^O eq 'amigaos' ) { $console = "CONSOLE:"; } elsif ($^O eq 'VMS') { $console = 'sys$command'; } # Keep this penultimate, on the grounds that it satisfies a wide variety of # Unix-like systems that would otherwise need to be identified individually. elsif ( -e "/dev/tty" ) { $console = "/dev/tty"; } # Keep this last. else { _db_warn("Can't figure out your console, using stdin"); undef $console; } if ( ( $^O eq 'MSWin32' ) and ( $slave_editor or defined $ENV{EMACS} ) ) { # /dev/tty is binary. use stdin for textmode $console = undef; } if ( $^O eq 'NetWare' ) { # /dev/tty is binary. use stdin for textmode $console = undef; } # In OS/2, we need to use STDIN to get textmode too, even though # it pretty much looks like Unix otherwise. if ( defined $ENV{OS2_SHELL} and ( $slave_editor or $ENV{WINDOWID} ) ) { # In OS/2 $console = undef; } $console = $tty if defined $tty; # Handle socket stuff. if ( defined $remoteport ) { # If RemotePort was defined in the options, connect input and output # to the socket. $IN = $OUT = connect_remoteport(); } ## end if (defined $remoteport) # Non-socket. else { # Two debuggers running (probably a system or a backtick that invokes # the debugger itself under the running one). create a new IN and OUT # filehandle, and do the necessary mojo to create a new tty if we # know how, and we can. create_IN_OUT(4) if $CreateTTY & 4; if ($console) { # If we have a console, check to see if there are separate ins and # outs to open. (They are assumed identical if not.) my ( $i, $o ) = split /,/, $console; $o = $i unless defined $o; # read/write on in, or just read, or read on STDIN. open( IN, '+<', $i ) || open( IN, '<', $i ) || open( IN, "<&STDIN" ); # read/write/create/clobber out, or write/create/clobber out, # or merge with STDERR, or merge with STDOUT. open( OUT, '+>', $o ) || open( OUT, '>', $o ) || open( OUT, ">&STDERR" ) || open( OUT, ">&STDOUT" ); # so we don't dongle stdout } ## end if ($console) elsif ( not defined $console ) { # No console. Open STDIN. open( IN, "<&STDIN" ); # merge with STDERR, or with STDOUT. open( OUT, ">&STDERR" ) || open( OUT, ">&STDOUT" ); # so we don't dongle stdout $console = 'STDIN/OUT'; } ## end elsif (not defined $console) # Keep copies of the filehandles so that when the pager runs, it # can close standard input without clobbering ours. if ($console or (not defined($console))) { $IN = \*IN; $OUT = \*OUT; } } ## end elsif (from if(defined $remoteport)) # Unbuffer DB::OUT. We need to see responses right away. _autoflush($OUT); # Line info goes to debugger output unless pointed elsewhere. # Pointing elsewhere makes it possible for slave editors to # keep track of file and position. We have both a filehandle # and a I/O description to keep track of. $LINEINFO = $OUT unless defined $LINEINFO; $lineinfo = $console unless defined $lineinfo; # share($LINEINFO); # <- unable to share globs share($lineinfo); # # Show the debugger greeting. $header =~ s/.Header: ([^,]+),v(\s+\S+\s+\S+).*$/$1$2/; unless ($runnonstop) { local $\ = ''; local $, = ''; if ( $term_pid eq '-1' ) { print $OUT "\nDaughter DB session started...\n"; } else { print $OUT "\nLoading DB routines from $header\n"; print $OUT ( "Editor support ", $slave_editor ? "enabled" : "available", ".\n" ); print $OUT "\nEnter h or 'h h' for help, or '$doccmd perldebug' for more help.\n\n"; } ## end else [ if ($term_pid eq '-1') } ## end unless ($runnonstop) } ## end else [ if ($notty) # XXX This looks like a bug to me. # Why copy to @ARGS and then futz with @args? @ARGS = @ARGV; # for (@args) { # Make sure backslashes before single quotes are stripped out, and # keep args unless they are numeric (XXX why?) # s/\'/\\\'/g; # removed while not justified understandably # s/(.*)/'$1'/ unless /^-?[\d.]+$/; # ditto # } # If there was an afterinit() sub defined, call it. It will get # executed in our scope, so it can fiddle with debugger globals. if ( defined &afterinit ) { # May be defined in $rcfile afterinit(); } # Inform us about "Stack dump during die enabled ..." in dieLevel(). use vars qw($I_m_init); $I_m_init = 1; ############################################################ Subroutines # $cmd cannot be an our() variable unfortunately (possible perl bug?). use vars qw( $action $cmd $file $filename_ini $finished %had_breakpoints $level $max $package $try ); our ( %alias, $doret, $end, $fall_off_end, $incr, $laststep, $rc, $sh, $stack_depth, @stack, @to_watch, @old_watch, ); sub _DB__determine_if_we_should_break { # if we have something here, see if we should break. # $stop is lexical and local to this block - $action on the other hand # is global. my $stop; if ( $dbline{$line} && _is_breakpoint_enabled($filename, $line) && (( $stop, $action ) = split( /\0/, $dbline{$line} ) ) ) { # Stop if the stop criterion says to just stop. if ( $stop eq '1' ) { $signal |= 1; } # It's a conditional stop; eval it in the user's context and # see if we should stop. If so, remove the one-time sigil. elsif ($stop) { $evalarg = "\$DB::signal |= 1 if do {$stop}"; # The &-call is here to ascertain the mutability of @_. &DB::eval; # If the breakpoint is temporary, then delete its enabled status. if ($dbline{$line} =~ s/;9($|\0)/$1/) { _cancel_breakpoint_temp_enabled_status($filename, $line); } } } ## end if ($dbline{$line} && ... } sub _DB__is_finished { if ($finished and $level <= 1) { end_report(); return 1; } else { return; } } sub _DB__read_next_cmd { my ($tid) = @_; # We have a terminal, or can get one ... if (!$term) { setterm(); } # ... and it belongs to this PID or we get one for this PID ... if ($term_pid != $$) { resetterm(1); } # ... and we got a line of command input ... $cmd = DB::readline( "$pidprompt $tid DB" . ( '<' x $level ) . ( $#hist + 1 ) . ( '>' x $level ) . " " ); return defined($cmd); } sub _DB__trim_command_and_return_first_component { my ($obj) = @_; $cmd =~ s/\A\s+//s; # trim annoying leading whitespace $cmd =~ s/\s+\z//s; # trim annoying trailing whitespace # A single-character debugger command can be immediately followed by its # argument if they aren't both alphanumeric; otherwise require space # between commands and arguments: my ($verb, $args) = $cmd =~ m{\A(.\b|\S*)\s*(.*)}s; $obj->cmd_verb($verb); $obj->cmd_args($args); return; } sub _DB__handle_f_command { my ($obj) = @_; if ($file = $obj->cmd_args) { # help for no arguments (old-style was return from sub). if ( !$file ) { print $OUT "The old f command is now the r command.\n"; # hint print $OUT "The new f command switches filenames.\n"; next CMD; } ## end if (!$file) # if not in magic file list, try a close match. if ( !defined $main::{ '_<' . $file } ) { if ( ($try) = grep( m#^_<.*$file#, keys %main:: ) ) { { $try = substr( $try, 2 ); print $OUT "Choosing $try matching '$file':\n"; $file = $try; } } ## end if (($try) = grep(m#^_<.*$file#... } ## end if (!defined $main::{ ... # If not successfully switched now, we failed. if ( !defined $main::{ '_<' . $file } ) { print $OUT "No file matching '$file' is loaded.\n"; next CMD; } # We switched, so switch the debugger internals around. elsif ( $file ne $filename ) { *dbline = $main::{ '_<' . $file }; $max = $#dbline; $filename = $file; $start = 1; $cmd = "l"; } ## end elsif ($file ne $filename) # We didn't switch; say we didn't. else { print $OUT "Already in $file.\n"; next CMD; } } return; } sub _DB__handle_dot_command { my ($obj) = @_; # . command. if ($obj->_is_full('.')) { $incr = -1; # stay at current line # Reset everything to the old location. $start = $line; $filename = $filename_ini; *dbline = $main::{ '_<' . $filename }; $max = $#dbline; # Now where are we? print_lineinfo($obj->position()); next CMD; } return; } sub _DB__handle_y_command { my ($obj) = @_; if (my ($match_level, $match_vars) = $obj->cmd_args =~ /\A(?:(\d*)\s*(.*))?\z/) { # See if we've got the necessary support. if (!eval { local @INC = @INC; pop @INC if $INC[-1] eq '.'; require PadWalker; PadWalker->VERSION(0.08) }) { my $Err = $@; _db_warn( $Err =~ /locate/ ? "PadWalker module not found - please install\n" : $Err ); next CMD; } # Load up dumpvar if we don't have it. If we can, that is. do 'dumpvar.pl' || die $@ unless defined &main::dumpvar; defined &main::dumpvar or print $OUT "dumpvar.pl not available.\n" and next CMD; # Got all the modules we need. Find them and print them. my @vars = split( ' ', $match_vars || '' ); # Find the pad. my $h = eval { PadWalker::peek_my( ( $match_level || 0 ) + 2 ) }; # Oops. Can't find it. if (my $Err = $@) { $Err =~ s/ at .*//; _db_warn($Err); next CMD; } # Show the desired vars with dumplex(). my $savout = select($OUT); # Have dumplex dump the lexicals. foreach my $key (sort keys %$h) { dumpvar::dumplex( $key, $h->{$key}, defined $option{dumpDepth} ? $option{dumpDepth} : -1, @vars ); } select($savout); next CMD; } } sub _DB__handle_c_command { my ($obj) = @_; my $i = $obj->cmd_args; if ($i =~ m#\A[\w:]*\z#) { # Hey, show's over. The debugged program finished # executing already. next CMD if _DB__is_finished(); # Capture the place to put a one-time break. $subname = $i; # Probably not needed, since we finish an interactive # sub-session anyway... # local $filename = $filename; # local *dbline = *dbline; # XXX Would this work?! # # The above question wonders if localizing the alias # to the magic array works or not. Since it's commented # out, we'll just leave that to speculation for now. # If the "subname" isn't all digits, we'll assume it # is a subroutine name, and try to find it. if ( $subname =~ /\D/ ) { # subroutine name # Qualify it to the current package unless it's # already qualified. $subname = $package . "::" . $subname unless $subname =~ /::/; # find_sub will return "file:line_number" corresponding # to where the subroutine is defined; we call find_sub, # break up the return value, and assign it in one # operation. ( $file, $i ) = ( find_sub($subname) =~ /^(.*):(.*)$/ ); # Force the line number to be numeric. $i = $i + 0; # If we got a line number, we found the sub. if ($i) { # Switch all the debugger's internals around so # we're actually working with that file. $filename = $file; *dbline = $main::{ '_<' . $filename }; # Mark that there's a breakpoint in this file. $had_breakpoints{$filename} |= 1; # Scan forward to the first executable line # after the 'sub whatever' line. $max = $#dbline; my $_line_num = $i; while ($dbline[$_line_num] == 0 && $_line_num< $max) { $_line_num++; } $i = $_line_num; } ## end if ($i) # We didn't find a sub by that name. else { print $OUT "Subroutine $subname not found.\n"; next CMD; } } ## end if ($subname =~ /\D/) # At this point, either the subname was all digits (an # absolute line-break request) or we've scanned through # the code following the definition of the sub, looking # for an executable, which we may or may not have found. # # If $i (which we set $subname from) is non-zero, we # got a request to break at some line somewhere. On # one hand, if there wasn't any real subroutine name # involved, this will be a request to break in the current # file at the specified line, so we have to check to make # sure that the line specified really is breakable. # # On the other hand, if there was a subname supplied, the # preceding block has moved us to the proper file and # location within that file, and then scanned forward # looking for the next executable line. We have to make # sure that one was found. # # On the gripping hand, we can't do anything unless the # current value of $i points to a valid breakable line. # Check that. if ($i) { # Breakable? if ( $dbline[$i] == 0 ) { print $OUT "Line $i not breakable.\n"; next CMD; } # Yes. Set up the one-time-break sigil. $dbline{$i} =~ s/($|\0)/;9$1/; # add one-time-only b.p. _enable_breakpoint_temp_enabled_status($filename, $i); } ## end if ($i) # Turn off stack tracing from here up. for my $j (0 .. $stack_depth) { $stack[ $j ] &= ~1; } last CMD; } return; } sub _DB__handle_forward_slash_command { my ($obj) = @_; # The pattern as a string. use vars qw($inpat); if (($inpat) = $cmd =~ m#\A/(.*)\z#) { # Remove the final slash. $inpat =~ s:([^\\])/$:$1:; # If the pattern isn't null ... if ( $inpat ne "" ) { # Turn off warn and die processing for a bit. local $SIG{__DIE__}; local $SIG{__WARN__}; # Create the pattern. eval 'no strict q/vars/; $inpat =~ m' . "\a$inpat\a"; if ( $@ ne "" ) { # Oops. Bad pattern. No biscuit. # Print the eval error and go back for more # commands. print {$OUT} "$@"; next CMD; } $obj->pat($inpat); } ## end if ($inpat ne "") # Set up to stop on wrap-around. $end = $start; # Don't move off the current line. $incr = -1; my $pat = $obj->pat; # Done in eval so nothing breaks if the pattern # does something weird. eval { no strict q/vars/; for (;;) { # Move ahead one line. ++$start; # Wrap if we pass the last line. if ($start > $max) { $start = 1; } # Stop if we have gotten back to this line again, last if ($start == $end); # A hit! (Note, though, that we are doing # case-insensitive matching. Maybe a qr// # expression would be better, so the user could # do case-sensitive matching if desired. if ($dbline[$start] =~ m/$pat/i) { if ($slave_editor) { # Handle proper escaping in the slave. print {$OUT} "\032\032$filename:$start:0\n"; } else { # Just print the line normally. print {$OUT} "$start:\t",$dbline[$start],"\n"; } # And quit since we found something. last; } } }; if ($@) { warn $@; } # If we wrapped, there never was a match. if ( $start == $end ) { print {$OUT} "/$pat/: not found\n"; } next CMD; } return; } sub _DB__handle_question_mark_command { my ($obj) = @_; # ? - backward pattern search. if (my ($inpat) = $cmd =~ m#\A\?(.*)\z#) { # Get the pattern, remove trailing question mark. $inpat =~ s:([^\\])\?$:$1:; # If we've got one ... if ( $inpat ne "" ) { # Turn off die & warn handlers. local $SIG{__DIE__}; local $SIG{__WARN__}; eval '$inpat =~ m' . "\a$inpat\a"; if ( $@ ne "" ) { # Ouch. Not good. Print the error. print $OUT $@; next CMD; } $obj->pat($inpat); } ## end if ($inpat ne "") # Where we are now is where to stop after wraparound. $end = $start; # Don't move away from this line. $incr = -1; my $pat = $obj->pat; # Search inside the eval to prevent pattern badness # from killing us. eval { no strict q/vars/; for (;;) { # Back up a line. --$start; # Wrap if we pass the first line. $start = $max if ($start <= 0); # Quit if we get back where we started, last if ($start == $end); # Match? if ($dbline[$start] =~ m/$pat/i) { if ($slave_editor) { # Yep, follow slave editor requirements. print $OUT "\032\032$filename:$start:0\n"; } else { # Yep, just print normally. print $OUT "$start:\t",$dbline[$start],"\n"; } # Found, so done. last; } } }; # Say we failed if the loop never found anything, if ( $start == $end ) { print {$OUT} "?$pat?: not found\n"; } next CMD; } return; } sub _DB__handle_restart_and_rerun_commands { my ($obj) = @_; my $cmd_cmd = $obj->cmd_verb; my $cmd_params = $obj->cmd_args; # R - restart execution. # rerun - controlled restart execution. if ($cmd_cmd eq 'rerun' or $cmd_params eq '') { # Change directory to the initial current working directory on # the script startup, so if the debugged program changed the # directory, then we will still be able to find the path to the # the program. (perl 5 RT #121509 ). chdir ($_initial_cwd); my @args = ($cmd_cmd eq 'R' ? restart() : rerun($cmd_params)); # Close all non-system fds for a clean restart. A more # correct method would be to close all fds that were not # open when the process started, but this seems to be # hard. See "debugger 'R'estart and open database # connections" on p5p. my $max_fd = 1024; # default if POSIX can't be loaded if (eval { require POSIX }) { eval { $max_fd = POSIX::sysconf(POSIX::_SC_OPEN_MAX()) }; } if (defined $max_fd) { foreach ($^F+1 .. $max_fd-1) { next unless open FD_TO_CLOSE, "<&=$_"; close(FD_TO_CLOSE); } } # And run Perl again. We use exec() to keep the # PID stable (and that way $ini_pids is still valid). exec(@args) or print {$OUT} "exec failed: $!\n"; last CMD; } return; } sub _DB__handle_run_command_in_pager_command { my ($obj) = @_; if ($cmd =~ m#\A\|\|?\s*[^|]#) { if ( $pager =~ /^\|/ ) { # Default pager is into a pipe. Redirect I/O. open( SAVEOUT, ">&STDOUT" ) || _db_warn("Can't save STDOUT"); open( STDOUT, ">&OUT" ) || _db_warn("Can't redirect STDOUT"); } ## end if ($pager =~ /^\|/) else { # Not into a pipe. STDOUT is safe. open( SAVEOUT, ">&OUT" ) || _db_warn("Can't save DB::OUT"); } # Fix up environment to record we have less if so. fix_less(); unless ( $obj->piped(scalar ( open( OUT, $pager ) ) ) ) { # Couldn't open pipe to pager. _db_warn("Can't pipe output to '$pager'"); if ( $pager =~ /^\|/ ) { # Redirect I/O back again. open( OUT, ">&STDOUT" ) # XXX: lost message || _db_warn("Can't restore DB::OUT"); open( STDOUT, ">&SAVEOUT" ) || _db_warn("Can't restore STDOUT"); close(SAVEOUT); } ## end if ($pager =~ /^\|/) else { # Redirect I/O. STDOUT already safe. open( OUT, ">&STDOUT" ) # XXX: lost message || _db_warn("Can't restore DB::OUT"); } next CMD; } ## end unless ($piped = open(OUT,... # Set up broken-pipe handler if necessary. $SIG{PIPE} = \&DB::catch if $pager =~ /^\|/ && ( "" eq $SIG{PIPE} || "DEFAULT" eq $SIG{PIPE} ); _autoflush(\*OUT); # Save current filehandle, and put it back. $obj->selected(scalar( select(OUT) )); # Don't put it back if pager was a pipe. if ($cmd !~ /\A\|\|/) { select($obj->selected()); $obj->selected(""); } # Trim off the pipe symbols and run the command now. $cmd =~ s#\A\|+\s*##; redo PIPE; } return; } sub _DB__handle_m_command { my ($obj) = @_; if ($cmd =~ s#\Am\s+([\w:]+)\s*\z# #) { methods($1); next CMD; } # m expr - set up DB::eval to do the work if ($cmd =~ s#\Am\b# #) { # Rest gets done by DB::eval() $onetimeDump = 'methods'; # method output gets used there } return; } sub _DB__at_end_of_every_command { my ($obj) = @_; # At the end of every command: if ($obj->piped) { # Unhook the pipe mechanism now. if ( $pager =~ /^\|/ ) { # No error from the child. $? = 0; # we cannot warn here: the handle is missing --tchrist close(OUT) || print SAVEOUT "\nCan't close DB::OUT\n"; # most of the $? crud was coping with broken cshisms # $? is explicitly set to 0, so this never runs. if ($?) { print SAVEOUT "Pager '$pager' failed: "; if ( $? == -1 ) { print SAVEOUT "shell returned -1\n"; } elsif ( $? >> 8 ) { print SAVEOUT ( $? & 127 ) ? " (SIG#" . ( $? & 127 ) . ")" : "", ( $? & 128 ) ? " -- core dumped" : "", "\n"; } else { print SAVEOUT "status ", ( $? >> 8 ), "\n"; } } ## end if ($?) # Reopen filehandle for our output (if we can) and # restore STDOUT (if we can). open( OUT, ">&STDOUT" ) || _db_warn("Can't restore DB::OUT"); open( STDOUT, ">&SAVEOUT" ) || _db_warn("Can't restore STDOUT"); # Turn off pipe exception handler if necessary. $SIG{PIPE} = "DEFAULT" if $SIG{PIPE} eq \&DB::catch; # Will stop ignoring SIGPIPE if done like nohup(1) # does SIGINT but Perl doesn't give us a choice. } ## end if ($pager =~ /^\|/) else { # Non-piped "pager". Just restore STDOUT. open( OUT, ">&SAVEOUT" ) || _db_warn("Can't restore DB::OUT"); } # Let Readline know about the new filehandles. reset_IN_OUT( \*IN, \*OUT ); # Close filehandle pager was using, restore the normal one # if necessary, close(SAVEOUT); if ($obj->selected() ne "") { select($obj->selected); $obj->selected(""); } # No pipes now. $obj->piped(""); } ## end if ($piped) return; } sub _DB__handle_watch_expressions { my $self = shift; if ( $DB::trace & 2 ) { for my $n (0 .. $#DB::to_watch) { $DB::evalarg = $DB::to_watch[$n]; local $DB::onetimeDump; # Tell DB::eval() to not output results # Fix context DB::eval() wants to return an array, but # we need a scalar here. my ($val) = join( "', '", DB::eval(@_) ); $val = ( ( defined $val ) ? "'$val'" : 'undef' ); # Did it change? if ( $val ne $DB::old_watch[$n] ) { # Yep! Show the difference, and fake an interrupt. $DB::signal = 1; print {$DB::OUT} < { t => 'm', v => '_handle_dash_command', }, '.' => { t => 's', v => \&_DB__handle_dot_command, }, '=' => { t => 'm', v => '_handle_equal_sign_command', }, 'H' => { t => 'm', v => '_handle_H_command', }, 'S' => { t => 'm', v => '_handle_S_command', }, 'T' => { t => 'm', v => '_handle_T_command', }, 'W' => { t => 'm', v => '_handle_W_command', }, 'c' => { t => 's', v => \&_DB__handle_c_command, }, 'f' => { t => 's', v => \&_DB__handle_f_command, }, 'm' => { t => 's', v => \&_DB__handle_m_command, }, 'n' => { t => 'm', v => '_handle_n_command', }, 'p' => { t => 'm', v => '_handle_p_command', }, 'q' => { t => 'm', v => '_handle_q_command', }, 'r' => { t => 'm', v => '_handle_r_command', }, 's' => { t => 'm', v => '_handle_s_command', }, 'save' => { t => 'm', v => '_handle_save_command', }, 'source' => { t => 'm', v => '_handle_source_command', }, 't' => { t => 'm', v => '_handle_t_command', }, 'w' => { t => 'm', v => '_handle_w_command', }, 'x' => { t => 'm', v => '_handle_x_command', }, 'y' => { t => 's', v => \&_DB__handle_y_command, }, (map { $_ => { t => 'm', v => '_handle_V_command_and_X_command', }, } ('X', 'V')), (map { $_ => { t => 'm', v => '_handle_enable_disable_commands', }, } qw(enable disable)), (map { $_ => { t => 's', v => \&_DB__handle_restart_and_rerun_commands, }, } qw(R rerun)), (map { $_ => {t => 'm', v => '_handle_cmd_wrapper_commands' }, } qw(a A b B e E h i l L M o O v w W)), ); }; sub DB { # lock the debugger and get the thread id for the prompt lock($DBGR); my $tid; my $position; my ($prefix, $after, $infix); my $pat; my $explicit_stop; my $piped; my $selected; if ($ENV{PERL5DB_THREADED}) { $tid = eval { "[".threads->tid."]" }; } my $cmd_verb; my $cmd_args; my $obj = DB::Obj->new( { position => \$position, prefix => \$prefix, after => \$after, explicit_stop => \$explicit_stop, infix => \$infix, cmd_args => \$cmd_args, cmd_verb => \$cmd_verb, pat => \$pat, piped => \$piped, selected => \$selected, }, ); $obj->_DB_on_init__initialize_globals(@_); # Preserve current values of $@, $!, $^E, $,, $/, $\, $^W. # The code being debugged may have altered them. DB::save(); # Since DB::DB gets called after every line, we can use caller() to # figure out where we last were executing. Sneaky, eh? This works because # caller is returning all the extra information when called from the # debugger. local ( $package, $filename, $line ) = caller; $filename_ini = $filename; # set up the context for DB::eval, so it can properly execute # code on behalf of the user. We add the package in so that the # code is eval'ed in the proper package (not in the debugger!). local $usercontext = _calc_usercontext($package); # Create an alias to the active file magical array to simplify # the code here. local (*dbline) = $main::{ '_<' . $filename }; # Last line in the program. $max = $#dbline; # The &-call is here to ascertain the mutability of @_. &_DB__determine_if_we_should_break; # Preserve the current stop-or-not, and see if any of the W # (watch expressions) has changed. my $was_signal = $signal; # If we have any watch expressions ... _DB__handle_watch_expressions($obj); # If there's a user-defined DB::watchfunction, call it with the # current package, filename, and line. The function executes in # the DB:: package. if ( $trace & 4 ) { # User-installed watch return if watchfunction( $package, $filename, $line ) and not $single and not $was_signal and not( $trace & ~4 ); } ## end if ($trace & 4) # Pick up any alteration to $signal in the watchfunction, and # turn off the signal now. $was_signal = $signal; $signal = 0; # Make sure that we always print if asked for explicitly regardless # of $trace_to_depth . $explicit_stop = ($single || $was_signal); # Check to see if we should grab control ($single true, # trace set appropriately, or we got a signal). if ( $explicit_stop || ( $trace & 1 ) ) { $obj->_DB__grab_control(@_); } ## end if ($single || ($trace... # If there's an action, do it now. if ($action) { $evalarg = $action; # The &-call is here to ascertain the mutability of @_. &DB::eval; } # Are we nested another level (e.g., did we evaluate a function # that had a breakpoint in it at the debugger prompt)? if ( $single || $was_signal ) { # Yes, go down a level. local $level = $level + 1; # Do any pre-prompt actions. foreach $evalarg (@$pre) { # The &-call is here to ascertain the mutability of @_. &DB::eval; } # Complain about too much recursion if we passed the limit. if ($single & 4) { print $OUT $stack_depth . " levels deep in subroutine calls!\n"; } # The line we're currently on. Set $incr to -1 to stay here # until we get a command that tells us to advance. $start = $line; $incr = -1; # for backward motion. # Tack preprompt debugger actions ahead of any actual input. @typeahead = ( @$pretype, @typeahead ); # The big command dispatch loop. It keeps running until the # user yields up control again. # # If we have a terminal for input, and we get something back # from readline(), keep on processing. CMD: while (_DB__read_next_cmd($tid)) { share($cmd); # ... try to execute the input as debugger commands. # Don't stop running. $single = 0; # No signal is active. $signal = 0; # Handle continued commands (ending with \): if ($cmd =~ s/\\\z/\n/) { $cmd .= DB::readline(" cont: "); redo CMD; } # Empty input means repeat the last command. if ($cmd eq '') { $cmd = $laststep; } chomp($cmd); # get rid of the annoying extra newline if (length($cmd) >= 2) { push( @hist, $cmd ); } push( @truehist, $cmd ); share(@hist); share(@truehist); # This is a restart point for commands that didn't arrive # via direct user input. It allows us to 'redo PIPE' to # re-execute command processing without reading a new command. PIPE: { _DB__trim_command_and_return_first_component($obj); # See if there's an alias for the command, and set it up if so. if ( $alias{$cmd_verb} ) { # Squelch signal handling; we want to keep control here # if something goes loco during the alias eval. local $SIG{__DIE__}; local $SIG{__WARN__}; # This is a command, so we eval it in the DEBUGGER's # scope! Otherwise, we can't see the special debugger # variables, or get to the debugger's subs. (Well, we # _could_, but why make it even more complicated?) eval "\$cmd =~ $alias{$cmd_verb}"; if ($@) { local $\ = ''; print $OUT "Couldn't evaluate '$cmd_verb' alias: $@"; next CMD; } _DB__trim_command_and_return_first_component($obj); } ## end if ($alias{$cmd_verb}) # All of these commands were remapped in perl 5.8.0; # we send them off to the secondary dispatcher (see below). $obj->_handle_special_char_cmd_wrapper_commands; _DB__trim_command_and_return_first_component($obj); if (my $cmd_rec = $cmd_lookup{$cmd_verb}) { my $type = $cmd_rec->{t}; my $val = $cmd_rec->{v}; if ($type eq 'm') { $obj->$val(); } elsif ($type eq 's') { $val->($obj); } } _DB__handle_forward_slash_command($obj); _DB__handle_question_mark_command($obj); # $rc - recall command. $obj->_handle_rc_recall_command; $obj->_handle_sh_command; $obj->_handle_rc_search_history_command; $obj->_handle_doc_command; # || - run command in the pager, with output to DB::OUT. _DB__handle_run_command_in_pager_command($obj); } # PIPE: # trace an expression $cmd =~ s/^t\s/\$DB::trace |= 1;\n/; # Make sure the flag that says "the debugger's running" is # still on, to make sure we get control again. $evalarg = "\$^D = \$^D | \$DB::db_stop;\n$cmd"; # Run *our* eval that executes in the caller's context. # The &-call is here to ascertain the mutability of @_. &DB::eval; # Turn off the one-time-dump stuff now. if ($onetimeDump) { $onetimeDump = undef; $onetimedumpDepth = undef; } elsif ( $term_pid == $$ ) { eval { # May run under miniperl, when not available... STDOUT->flush(); STDERR->flush(); }; # XXX If this is the master pid, print a newline. print {$OUT} "\n"; } } ## end while (($term || &setterm... continue { # CMD: _DB__at_end_of_every_command($obj); } # CMD: # No more commands? Quit. $fall_off_end = 1 unless defined $cmd; # Emulate 'q' on EOF # Evaluate post-prompt commands. foreach $evalarg (@$post) { # The &-call is here to ascertain the mutability of @_. &DB::eval; } } # if ($single || $signal) # Put the user's globals back where you found them. ( $@, $!, $^E, $,, $/, $\, $^W ) = @saved; (); } ## end sub DB # Because DB::Obj is used above, # # my $obj = DB::Obj->new( # # The following package declaration must come before that, # or else runtime errors will occur with # # PERLDB_OPTS="autotrace nonstop" # # ( rt#116771 ) BEGIN { package DB::Obj; sub new { my $class = shift; my $self = bless {}, $class; $self->_init(@_); return $self; } sub _init { my ($self, $args) = @_; %{$self} = (%$self, %$args); return; } { no strict 'refs'; foreach my $slot_name (qw( after explicit_stop infix pat piped position prefix selected cmd_verb cmd_args )) { my $slot = $slot_name; *{$slot} = sub { my $self = shift; if (@_) { ${ $self->{$slot} } = shift; } return ${ $self->{$slot} }; }; *{"append_to_$slot"} = sub { my $self = shift; my $s = shift; return $self->$slot($self->$slot . $s); }; } } sub _DB_on_init__initialize_globals { my $self = shift; # Check for whether we should be running continuously or not. # _After_ the perl program is compiled, $single is set to 1: if ( $single and not $second_time++ ) { # Options say run non-stop. Run until we get an interrupt. if ($runnonstop) { # Disable until signal # If there's any call stack in place, turn off single # stepping into subs throughout the stack. for my $i (0 .. $stack_depth) { $stack[ $i ] &= ~1; } # And we are now no longer in single-step mode. $single = 0; # If we simply returned at this point, we wouldn't get # the trace info. Fall on through. # return; } ## end if ($runnonstop) elsif ($ImmediateStop) { # We are supposed to stop here; XXX probably a break. $ImmediateStop = 0; # We've processed it; turn it off $signal = 1; # Simulate an interrupt to force # us into the command loop } } ## end if ($single and not $second_time... # If we're in single-step mode, or an interrupt (real or fake) # has occurred, turn off non-stop mode. $runnonstop = 0 if $single or $signal; return; } sub _my_print_lineinfo { my ($self, $i, $incr_pos) = @_; if ($frame) { # Print it indented if tracing is on. DB::print_lineinfo( ' ' x $stack_depth, "$i:\t$DB::dbline[$i]" . $self->after ); } else { DB::depth_print_lineinfo($self->explicit_stop, $incr_pos); } } sub _curr_line { return $DB::dbline[$line]; } sub _is_full { my ($self, $letter) = @_; return ($DB::cmd eq $letter); } sub _DB__grab_control { my $self = shift; # Yes, grab control. if ($slave_editor) { # Tell the editor to update its position. $self->position("\032\032${DB::filename}:$line:0\n"); DB::print_lineinfo($self->position()); } elsif ( $DB::package eq 'DB::fake' ) { # Fallen off the end already. if (!$DB::term) { DB::setterm(); } DB::print_help(< to quit or B to restart, use B I to avoid stopping after program termination, B, B or B to get additional info. EOP # Set the DB::eval context appropriately. # At program termination disable any user actions. $DB::action = undef; $DB::package = 'main'; $DB::usercontext = DB::_calc_usercontext($DB::package); } ## end elsif ($package eq 'DB::fake') else { # Still somewhere in the midst of execution. Set up the # debugger prompt. $DB::sub =~ s/\'/::/; # Swap Perl 4 package separators (') to # Perl 5 ones (sorry, we don't print Klingon #module names) $self->prefix($DB::sub =~ /::/ ? "" : ($DB::package . '::')); $self->append_to_prefix( "$DB::sub(${DB::filename}:" ); $self->after( $self->_curr_line =~ /\n$/ ? '' : "\n" ); # Break up the prompt if it's really long. if ( length($self->prefix()) > 30 ) { $self->position($self->prefix . "$line):\n$line:\t" . $self->_curr_line . $self->after); $self->prefix(""); $self->infix(":\t"); } else { $self->infix("):\t"); $self->position( $self->prefix . $line. $self->infix . $self->_curr_line . $self->after ); } # Print current line info, indenting if necessary. $self->_my_print_lineinfo($line, $self->position); my $i; my $line_i = sub { return $DB::dbline[$i]; }; # Scan forward, stopping at either the end or the next # unbreakable line. for ( $i = $line + 1 ; $i <= $DB::max && $line_i->() == 0 ; ++$i ) { #{ vi # Drop out on null statements, block closers, and comments. last if $line_i->() =~ /^\s*[\;\}\#\n]/; # Drop out if the user interrupted us. last if $signal; # Append a newline if the line doesn't have one. Can happen # in eval'ed text, for instance. $self->after( $line_i->() =~ /\n$/ ? '' : "\n" ); # Next executable line. my $incr_pos = $self->prefix . $i . $self->infix . $line_i->() . $self->after; $self->append_to_position($incr_pos); $self->_my_print_lineinfo($i, $incr_pos); } ## end for ($i = $line + 1 ; $i... } ## end else [ if ($slave_editor) return; } sub _handle_t_command { my $self = shift; my $levels = $self->cmd_args(); if ((!length($levels)) or ($levels !~ /\D/)) { $trace ^= 1; local $\ = ''; $DB::trace_to_depth = $levels ? $stack_depth + $levels : 1E9; print {$OUT} "Trace = " . ( ( $trace & 1 ) ? ( $levels ? "on (to level $DB::trace_to_depth)" : "on" ) : "off" ) . "\n"; next CMD; } return; } sub _handle_S_command { my $self = shift; if (my ($print_all_subs, $should_reverse, $Spatt) = $self->cmd_args =~ /\A((!)?(.+))?\z/) { # $Spatt is the pattern (if any) to use. # Reverse scan? my $Srev = defined $should_reverse; # No args - print all subs. my $Snocheck = !defined $print_all_subs; # Need to make these sane here. local $\ = ''; local $, = ''; # Search through the debugger's magical hash of subs. # If $nocheck is true, just print the sub name. # Otherwise, check it against the pattern. We then use # the XOR trick to reverse the condition as required. foreach $subname ( sort( keys %sub ) ) { if ( $Snocheck or $Srev ^ ( $subname =~ /$Spatt/ ) ) { print $OUT $subname, "\n"; } } next CMD; } return; } sub _handle_V_command_and_X_command { my $self = shift; $DB::cmd =~ s/^X\b/V $DB::package/; # Bare V commands get the currently-being-debugged package # added. if ($self->_is_full('V')) { $DB::cmd = "V $DB::package"; } # V - show variables in package. if (my ($new_packname, $new_vars_str) = $DB::cmd =~ /\AV\b\s*(\S+)\s*(.*)/) { # Save the currently selected filehandle and # force output to debugger's filehandle (dumpvar # just does "print" for output). my $savout = select($OUT); # Grab package name and variables to dump. $packname = $new_packname; my @vars = split( ' ', $new_vars_str ); # If main::dumpvar isn't here, get it. do 'dumpvar.pl' || die $@ unless defined &main::dumpvar; if ( defined &main::dumpvar ) { # We got it. Turn off subroutine entry/exit messages # for the moment, along with return values. local $frame = 0; local $doret = -2; # must detect sigpipe failures - not catching # then will cause the debugger to die. eval { main::dumpvar( $packname, defined $option{dumpDepth} ? $option{dumpDepth} : -1, # assume -1 unless specified @vars ); }; # The die doesn't need to include the $@, because # it will automatically get propagated for us. if ($@) { die unless $@ =~ /dumpvar print failed/; } } ## end if (defined &main::dumpvar) else { # Couldn't load dumpvar. print $OUT "dumpvar.pl not available.\n"; } # Restore the output filehandle, and go round again. select($savout); next CMD; } return; } sub _handle_dash_command { my $self = shift; if ($self->_is_full('-')) { # back up by a window; go to 1 if back too far. $start -= $incr + $window + 1; $start = 1 if $start <= 0; $incr = $window - 1; # Generate and execute a "l +" command (handled below). $DB::cmd = 'l ' . ($start) . '+'; redo CMD; } return; } sub _n_or_s_commands_generic { my ($self, $new_val) = @_; # n - next next CMD if DB::_DB__is_finished(); # Single step, but don't enter subs. $single = $new_val; # Save for empty command (repeat last). $laststep = $DB::cmd; last CMD; } sub _n_or_s { my ($self, $letter, $new_val) = @_; if ($self->_is_full($letter)) { $self->_n_or_s_commands_generic($new_val); } else { $self->_n_or_s_and_arg_commands_generic($letter, $new_val); } return; } sub _handle_n_command { my $self = shift; return $self->_n_or_s('n', 2); } sub _handle_s_command { my $self = shift; return $self->_n_or_s('s', 1); } sub _handle_r_command { my $self = shift; # r - return from the current subroutine. if ($self->_is_full('r')) { # Can't do anything if the program's over. next CMD if DB::_DB__is_finished(); # Turn on stack trace. $stack[$stack_depth] |= 1; # Print return value unless the stack is empty. $doret = $option{PrintRet} ? $stack_depth - 1 : -2; last CMD; } return; } sub _handle_T_command { my $self = shift; if ($self->_is_full('T')) { DB::print_trace( $OUT, 1 ); # skip DB next CMD; } return; } sub _handle_w_command { my $self = shift; DB::cmd_w( 'w', $self->cmd_args() ); next CMD; return; } sub _handle_W_command { my $self = shift; if (my $arg = $self->cmd_args) { DB::cmd_W( 'W', $arg ); next CMD; } return; } sub _handle_rc_recall_command { my $self = shift; # $rc - recall command. if (my ($minus, $arg) = $DB::cmd =~ m#\A$rc+\s*(-)?(\d+)?\z#) { # No arguments, take one thing off history. pop(@hist) if length($DB::cmd) > 1; # Relative (- found)? # Y - index back from most recent (by 1 if bare minus) # N - go to that particular command slot or the last # thing if nothing following. $self->cmd_verb( scalar($minus ? ( $#hist - ( $arg || 1 ) ) : ( $arg || $#hist )) ); # Pick out the command desired. $DB::cmd = $hist[$self->cmd_verb]; # Print the command to be executed and restart the loop # with that command in the buffer. print {$OUT} $DB::cmd, "\n"; redo CMD; } return; } sub _handle_rc_search_history_command { my $self = shift; # $rc pattern $rc - find a command in the history. if (my ($arg) = $DB::cmd =~ /\A$rc([^$rc].*)\z/) { # Create the pattern to use. my $pat = "^$arg"; $self->pat($pat); # Toss off last entry if length is >1 (and it always is). pop(@hist) if length($DB::cmd) > 1; my $i; # Look backward through the history. SEARCH_HIST: for ( $i = $#hist ; $i ; --$i ) { # Stop if we find it. last SEARCH_HIST if $hist[$i] =~ /$pat/; } if ( !$i ) { # Never found it. print $OUT "No such command!\n\n"; next CMD; } # Found it. Put it in the buffer, print it, and process it. $DB::cmd = $hist[$i]; print $OUT $DB::cmd, "\n"; redo CMD; } return; } sub _handle_H_command { my $self = shift; if ($self->cmd_args =~ m#\A\*#) { @hist = @truehist = (); print $OUT "History cleansed\n"; next CMD; } if (my ($num) = $self->cmd_args =~ /\A(?:-(\d+))?/) { # Anything other than negative numbers is ignored by # the (incorrect) pattern, so this test does nothing. $end = $num ? ( $#hist - $num ) : 0; # Set to the minimum if less than zero. $hist = 0 if $hist < 0; # Start at the end of the array. # Stay in while we're still above the ending value. # Tick back by one each time around the loop. my $i; for ( $i = $#hist ; $i > $end ; $i-- ) { # Print the command unless it has no arguments. print $OUT "$i: ", $hist[$i], "\n" unless $hist[$i] =~ /^.?$/; } next CMD; } return; } sub _handle_doc_command { my $self = shift; # man, perldoc, doc - show manual pages. if (my ($man_page) = $DB::cmd =~ /\A(?:man|(?:perl)?doc)\b(?:\s+([^(]*))?\z/) { DB::runman($man_page); next CMD; } return; } sub _handle_p_command { my $self = shift; my $print_cmd = 'print {$DB::OUT} '; # p - print (no args): print $_. if ($self->_is_full('p')) { $DB::cmd = $print_cmd . '$_'; } else { # p - print the given expression. $DB::cmd =~ s/\Ap\b/$print_cmd /; } return; } sub _handle_equal_sign_command { my $self = shift; if ($DB::cmd =~ s/\A=\s*//) { my @keys; if ( length $DB::cmd == 0 ) { # No args, get current aliases. @keys = sort keys %alias; } elsif ( my ( $k, $v ) = ( $DB::cmd =~ /^(\S+)\s+(\S.*)/ ) ) { # Creating a new alias. $k is alias name, $v is # alias value. # can't use $_ or kill //g state for my $x ( $k, $v ) { # Escape "alarm" characters. $x =~ s/\a/\\a/g; } # Substitute key for value, using alarm chars # as separators (which is why we escaped them in # the command). $alias{$k} = "s\a$k\a$v\a"; # Turn off standard warn and die behavior. local $SIG{__DIE__}; local $SIG{__WARN__}; # Is it valid Perl? unless ( eval "sub { s\a$k\a$v\a }; 1" ) { # Nope. Bad alias. Say so and get out. print $OUT "Can't alias $k to $v: $@\n"; delete $alias{$k}; next CMD; } # We'll only list the new one. @keys = ($k); } ## end elsif (my ($k, $v) = ($DB::cmd... # The argument is the alias to list. else { @keys = ($DB::cmd); } # List aliases. for my $k (@keys) { # Messy metaquoting: Trim the substitution code off. # We use control-G as the delimiter because it's not # likely to appear in the alias. if ( ( my $v = $alias{$k} ) =~ ss\a$k\a(.*)\a$1 ) { # Print the alias. print $OUT "$k\t= $1\n"; } elsif ( defined $alias{$k} ) { # Couldn't trim it off; just print the alias code. print $OUT "$k\t$alias{$k}\n"; } else { # No such, dude. print "No alias for $k\n"; } } ## end for my $k (@keys) next CMD; } return; } sub _handle_source_command { my $self = shift; # source - read commands from a file (or pipe!) and execute. if (my $sourced_fn = $self->cmd_args) { if ( open my $fh, $sourced_fn ) { # Opened OK; stick it in the list of file handles. push @cmdfhs, $fh; } else { # Couldn't open it. DB::_db_warn("Can't execute '$sourced_fn': $!\n"); } next CMD; } return; } sub _handle_enable_disable_commands { my $self = shift; my $which_cmd = $self->cmd_verb; my $position = $self->cmd_args; if ($position !~ /\s/) { my ($fn, $line_num); if ($position =~ m{\A\d+\z}) { $fn = $DB::filename; $line_num = $position; } elsif (my ($new_fn, $new_line_num) = $position =~ m{\A(.*):(\d+)\z}) { ($fn, $line_num) = ($new_fn, $new_line_num); } else { DB::_db_warn("Wrong spec for enable/disable argument.\n"); } if (defined($fn)) { if (DB::_has_breakpoint_data_ref($fn, $line_num)) { DB::_set_breakpoint_enabled_status($fn, $line_num, ($which_cmd eq 'enable' ? 1 : '') ); } else { DB::_db_warn("No breakpoint set at ${fn}:${line_num}\n"); } } next CMD; } return; } sub _handle_save_command { my $self = shift; if (my $new_fn = $self->cmd_args) { my $filename = $new_fn || '.perl5dbrc'; # default? if ( open my $fh, '>', $filename ) { # chomp to remove extraneous newlines from source'd files chomp( my @truelist = map { m/\A\s*(save|source)/ ? "#$_" : $_ } @truehist ); print {$fh} join( "\n", @truelist ); print "commands saved in $filename\n"; } else { DB::_db_warn("Can't save debugger commands in '$new_fn': $!\n"); } next CMD; } return; } sub _n_or_s_and_arg_commands_generic { my ($self, $letter, $new_val) = @_; # s - single-step. Remember the last command was 's'. if ($DB::cmd =~ s#\A\Q$letter\E\s#\$DB::single = $new_val;\n#) { $laststep = $letter; } return; } sub _handle_sh_command { my $self = shift; # $sh$sh - run a shell command (if it's all ASCII). # Can't run shell commands with Unicode in the debugger, hmm. my $my_cmd = $DB::cmd; if ($my_cmd =~ m#\A$sh#gms) { if ($my_cmd =~ m#\G\z#cgms) { # Run the user's shell. If none defined, run Bourne. # We resume execution when the shell terminates. DB::_db_system( $ENV{SHELL} || "/bin/sh" ); next CMD; } elsif ($my_cmd =~ m#\G$sh\s*(.*)#cgms) { # System it. DB::_db_system($1); next CMD; } elsif ($my_cmd =~ m#\G\s*(.*)#cgms) { DB::_db_system( $ENV{SHELL} || "/bin/sh", "-c", $1 ); next CMD; } } } sub _handle_x_command { my $self = shift; if ($DB::cmd =~ s#\Ax\b# #) { # Remainder gets done by DB::eval() $onetimeDump = 'dump'; # main::dumpvar shows the output # handle special "x 3 blah" syntax XXX propagate # doc back to special variables. if ( $DB::cmd =~ s#\A\s*(\d+)(?=\s)# #) { $onetimedumpDepth = $1; } } return; } sub _handle_q_command { my $self = shift; if ($self->_is_full('q')) { $fall_off_end = 1; DB::clean_ENV(); exit $?; } return; } sub _handle_cmd_wrapper_commands { my $self = shift; DB::cmd_wrapper( $self->cmd_verb, $self->cmd_args, $line ); next CMD; } sub _handle_special_char_cmd_wrapper_commands { my $self = shift; # All of these commands were remapped in perl 5.8.0; # we send them off to the secondary dispatcher (see below). if (my ($cmd_letter, $my_arg) = $DB::cmd =~ /\A([<>\{]{1,2})\s*(.*)/so) { DB::cmd_wrapper( $cmd_letter, $my_arg, $line ); next CMD; } return; } } ## end DB::Obj package DB; # The following code may be executed now: # BEGIN {warn 4} use vars qw($deep); # We need to fully qualify the name ("DB::sub") to make "use strict;" # happy. -- Shlomi Fish sub _indent_print_line_info { my ($offset, $str) = @_; print_lineinfo( ' ' x ($stack_depth - $offset), $str); return; } sub _print_frame_message { my ($al) = @_; if ($frame) { if ($frame & 4) { # Extended frame entry message _indent_print_line_info(-1, "in "); # Why -1? But it works! :-( # Because print_trace will call add 1 to it and then call # dump_trace; this results in our skipping -1+1 = 0 stack frames # in dump_trace. # # Now it's 0 because we extracted a function. print_trace( $LINEINFO, 0, 1, 1, "$sub$al" ); } else { _indent_print_line_info(-1, "entering $sub$al\n" ); } } return; } sub DB::sub { # lock ourselves under threads lock($DBGR); # Whether or not the autoloader was running, a scalar to put the # sub's return value in (if needed), and an array to put the sub's # return value in (if needed). my ( $al, $ret, @ret ) = ""; if ($sub eq 'threads::new' && $ENV{PERL5DB_THREADED}) { print "creating new thread\n"; } # If the last ten characters are '::AUTOLOAD', note we've traced # into AUTOLOAD for $sub. if ( length($sub) > 10 && substr( $sub, -10, 10 ) eq '::AUTOLOAD' ) { no strict 'refs'; $al = " for $$sub" if defined $$sub; } # We stack the stack pointer and then increment it to protect us # from a situation that might unwind a whole bunch of call frames # at once. Localizing the stack pointer means that it will automatically # unwind the same amount when multiple stack frames are unwound. local $stack_depth = $stack_depth + 1; # Protect from non-local exits # Expand @stack. $#stack = $stack_depth; # Save current single-step setting. $stack[-1] = $single; # Turn off all flags except single-stepping. $single &= 1; # If we've gotten really deeply recursed, turn on the flag that will # make us stop with the 'deep recursion' message. $single |= 4 if $stack_depth == $deep; # If frame messages are on ... _print_frame_message($al); # standard frame entry message my $print_exit_msg = sub { # Check for exit trace messages... if ($frame & 2) { if ($frame & 4) # Extended exit message { _indent_print_line_info(0, "out "); print_trace( $LINEINFO, 0, 1, 1, "$sub$al" ); } else { _indent_print_line_info(0, "exited $sub$al\n" ); } } return; }; # Determine the sub's return type, and capture appropriately. if (wantarray) { # Called in array context. call sub and capture output. # DB::DB will recursively get control again if appropriate; we'll come # back here when the sub is finished. { no strict 'refs'; @ret = &$sub; } # Pop the single-step value back off the stack. $single |= $stack[ $stack_depth-- ]; $print_exit_msg->(); # Print the return info if we need to. if ( $doret eq $stack_depth or $frame & 16 ) { # Turn off output record separator. local $\ = ''; my $fh = ( $doret eq $stack_depth ? $OUT : $LINEINFO ); # Indent if we're printing because of $frame tracing. if ($frame & 16) { print {$fh} ' ' x $stack_depth; } # Print the return value. print {$fh} "list context return from $sub:\n"; dumpit( $fh, \@ret ); # And don't print it again. $doret = -2; } ## end if ($doret eq $stack_depth... # And we have to return the return value now. @ret; } ## end if (wantarray) # Scalar context. else { if ( defined wantarray ) { no strict 'refs'; # Save the value if it's wanted at all. $ret = &$sub; } else { no strict 'refs'; # Void return, explicitly. &$sub; undef $ret; } # Pop the single-step value off the stack. $single |= $stack[ $stack_depth-- ]; # If we're doing exit messages... $print_exit_msg->(); # If we are supposed to show the return value... same as before. if ( $doret eq $stack_depth or $frame & 16 and defined wantarray ) { local $\ = ''; my $fh = ( $doret eq $stack_depth ? $OUT : $LINEINFO ); print $fh ( ' ' x $stack_depth ) if $frame & 16; print $fh ( defined wantarray ? "scalar context return from $sub: " : "void context return from $sub\n" ); dumpit( $fh, $ret ) if defined wantarray; $doret = -2; } ## end if ($doret eq $stack_depth... # Return the appropriate scalar value. $ret; } ## end else [ if (wantarray) } ## end sub _sub sub lsub : lvalue { no strict 'refs'; # lock ourselves under threads lock($DBGR); # Whether or not the autoloader was running, a scalar to put the # sub's return value in (if needed), and an array to put the sub's # return value in (if needed). my ( $al, $ret, @ret ) = ""; if ($sub =~ /^threads::new$/ && $ENV{PERL5DB_THREADED}) { print "creating new thread\n"; } # If the last ten characters are C'::AUTOLOAD', note we've traced # into AUTOLOAD for $sub. if ( length($sub) > 10 && substr( $sub, -10, 10 ) eq '::AUTOLOAD' ) { $al = " for $$sub"; } # We stack the stack pointer and then increment it to protect us # from a situation that might unwind a whole bunch of call frames # at once. Localizing the stack pointer means that it will automatically # unwind the same amount when multiple stack frames are unwound. local $stack_depth = $stack_depth + 1; # Protect from non-local exits # Expand @stack. $#stack = $stack_depth; # Save current single-step setting. $stack[-1] = $single; # Turn off all flags except single-stepping. # Use local so the single-step value is popped back off the # stack for us. local $single = $single & 1; # If we've gotten really deeply recursed, turn on the flag that will # make us stop with the 'deep recursion' message. $single |= 4 if $stack_depth == $deep; # If frame messages are on ... _print_frame_message($al); # call the original lvalue sub. &$sub; } # Abstracting common code from multiple places elsewhere: sub depth_print_lineinfo { my $always_print = shift; print_lineinfo( @_ ) if ($always_print or $stack_depth < $trace_to_depth); } ### The API section my %set = ( # 'pre580' => { 'a' => 'pre580_a', 'A' => 'pre580_null', 'b' => 'pre580_b', 'B' => 'pre580_null', 'd' => 'pre580_null', 'D' => 'pre580_D', 'h' => 'pre580_h', 'M' => 'pre580_null', 'O' => 'o', 'o' => 'pre580_null', 'v' => 'M', 'w' => 'v', 'W' => 'pre580_W', }, 'pre590' => { '<' => 'pre590_prepost', '<<' => 'pre590_prepost', '>' => 'pre590_prepost', '>>' => 'pre590_prepost', '{' => 'pre590_prepost', '{{' => 'pre590_prepost', }, ); my %breakpoints_data; sub _has_breakpoint_data_ref { my ($filename, $line) = @_; return ( exists( $breakpoints_data{$filename} ) and exists( $breakpoints_data{$filename}{$line} ) ); } sub _get_breakpoint_data_ref { my ($filename, $line) = @_; return ($breakpoints_data{$filename}{$line} ||= +{}); } sub _delete_breakpoint_data_ref { my ($filename, $line) = @_; delete($breakpoints_data{$filename}{$line}); if (! scalar(keys( %{$breakpoints_data{$filename}} )) ) { delete($breakpoints_data{$filename}); } return; } sub _set_breakpoint_enabled_status { my ($filename, $line, $status) = @_; _get_breakpoint_data_ref($filename, $line)->{'enabled'} = ($status ? 1 : '') ; return; } sub _enable_breakpoint_temp_enabled_status { my ($filename, $line) = @_; _get_breakpoint_data_ref($filename, $line)->{'temp_enabled'} = 1; return; } sub _cancel_breakpoint_temp_enabled_status { my ($filename, $line) = @_; my $ref = _get_breakpoint_data_ref($filename, $line); delete ($ref->{'temp_enabled'}); if (! %$ref) { _delete_breakpoint_data_ref($filename, $line); } return; } sub _is_breakpoint_enabled { my ($filename, $line) = @_; my $data_ref = _get_breakpoint_data_ref($filename, $line); return ($data_ref->{'enabled'} || $data_ref->{'temp_enabled'}); } sub cmd_wrapper { my $cmd = shift; my $line = shift; my $dblineno = shift; # Assemble the command subroutine's name by looking up the # command set and command name in %set. If we can't find it, # default to the older version of the command. my $call = 'cmd_' . ( $set{$CommandSet}{$cmd} || ( $cmd =~ /\A[<>{]+/o ? 'prepost' : $cmd ) ); # Call the command subroutine, call it by name. return __PACKAGE__->can($call)->( $cmd, $line, $dblineno ); } ## end sub cmd_wrapper sub cmd_a { my $cmd = shift; my $line = shift || ''; # [.|line] expr my $dbline = shift; # If it's dot (here), or not all digits, use the current line. $line =~ s/\A\./$dbline/; # Should be a line number followed by an expression. if ( my ($lineno, $expr) = $line =~ /^\s*(\d*)\s*(\S.+)/ ) { if (! length($lineno)) { $lineno = $dbline; } # If we have an expression ... if ( length $expr ) { # ... but the line isn't breakable, complain. if ( $dbline[$lineno] == 0 ) { print $OUT "Line $lineno($dbline[$lineno]) does not have an action?\n"; } else { # It's executable. Record that the line has an action. $had_breakpoints{$filename} |= 2; # Remove any action, temp breakpoint, etc. $dbline{$lineno} =~ s/\0[^\0]*//; # Add the action to the line. $dbline{$lineno} .= "\0" . action($expr); _set_breakpoint_enabled_status($filename, $lineno, 1); } } ## end if (length $expr) } ## end if ($line =~ /^\s*(\d*)\s*(\S.+)/) else { # Syntax wrong. print $OUT "Adding an action requires an optional lineno and an expression\n" ; # hint } } ## end sub cmd_a sub cmd_A { my $cmd = shift; my $line = shift || ''; my $dbline = shift; # Dot is this line. $line =~ s/^\./$dbline/; # Call delete_action with a null param to delete them all. # The '1' forces the eval to be true. It'll be false only # if delete_action blows up for some reason, in which case # we print $@ and get out. if ( $line eq '*' ) { if (! eval { _delete_all_actions(); 1 }) { print {$OUT} $@; return; } } # There's a real line number. Pass it to delete_action. # Error trapping is as above. elsif ( $line =~ /^(\S.*)/ ) { if (! eval { delete_action($1); 1 }) { print {$OUT} $@; return; } } # Swing and a miss. Bad syntax. else { print $OUT "Deleting an action requires a line number, or '*' for all\n" ; # hint } } ## end sub cmd_A sub _remove_action_from_dbline { my $i = shift; $dbline{$i} =~ s/\0[^\0]*//; # \^a delete $dbline{$i} if $dbline{$i} eq ''; return; } sub _delete_all_actions { print {$OUT} "Deleting all actions...\n"; for my $file ( keys %had_breakpoints ) { local *dbline = $main::{ '_<' . $file }; $max = $#dbline; my $was; for my $i (1 .. $max) { if ( defined $dbline{$i} ) { _remove_action_from_dbline($i); } } unless ( $had_breakpoints{$file} &= ~2 ) { delete $had_breakpoints{$file}; } } return; } sub delete_action { my $i = shift; if ( defined($i) ) { # Can there be one? die "Line $i has no action .\n" if $dbline[$i] == 0; # Nuke whatever's there. _remove_action_from_dbline($i); } else { _delete_all_actions(); } } sub cmd_b { my $cmd = shift; my $line = shift; # [.|line] [cond] my $dbline = shift; my $default_cond = sub { my $cond = shift; return length($cond) ? $cond : '1'; }; # Make . the current line number if it's there.. $line =~ s/^\.(\s|\z)/$dbline$1/; # No line number, no condition. Simple break on current line. if ( $line =~ /^\s*$/ ) { cmd_b_line( $dbline, 1 ); } # Break on load for a file. elsif ( my ($file) = $line =~ /^load\b\s*(.*)/ ) { $file =~ s/\s+\z//; cmd_b_load($file); } # b compile|postpone [] # The interpreter actually traps this one for us; we just put the # necessary condition in the %postponed hash. elsif ( my ($action, $subname, $cond) = $line =~ /^(postpone|compile)\b\s*([':A-Za-z_][':\w]*)\s*(.*)/ ) { # De-Perl4-ify the name - ' separators to ::. $subname =~ s/'/::/g; # Qualify it into the current package unless it's already qualified. $subname = "${package}::" . $subname unless $subname =~ /::/; # Add main if it starts with ::. $subname = "main" . $subname if substr( $subname, 0, 2 ) eq "::"; # Save the break type for this sub. $postponed{$subname} = (($action eq 'postpone') ? ( "break +0 if " . $default_cond->($cond) ) : "compile"); } ## end elsif ($line =~ ... # b : [] elsif (my ($filename, $line_num, $cond) = $line =~ /\A(\S+[^:]):(\d+)\s*(.*)/ms) { cmd_b_filename_line( $filename, $line_num, (length($cond) ? $cond : '1'), ); } # b [] elsif ( my ($new_subname, $new_cond) = $line =~ /^([':A-Za-z_][':\w]*(?:\[.*\])?)\s*(.*)/ ) { # $subname = $new_subname; cmd_b_sub( $subname, $default_cond->($new_cond) ); } # b []. elsif ( my ($line_n, $cond) = $line =~ /^(\d*)\s*(.*)/ ) { # Capture the line. If none, it's the current line. $line = $line_n || $dbline; # Break on line. cmd_b_line( $line, $default_cond->($cond) ); } # Line didn't make sense. else { print "confused by line($line)?\n"; } return; } ## end sub cmd_b sub break_on_load { my $file = shift; $break_on_load{$file} = 1; $had_breakpoints{$file} |= 1; } sub report_break_on_load { sort keys %break_on_load; } sub cmd_b_load { my $file = shift; my @files; # This is a block because that way we can use a redo inside it # even without there being any looping structure at all outside it. { # Save short name and full path if found. push @files, $file; push @files, $::INC{$file} if $::INC{$file}; # Tack on .pm and do it again unless there was a '.' in the name # already. $file .= '.pm', redo unless $file =~ /\./; } # Do the real work here. break_on_load($_) for @files; # All the files that have break-on-load breakpoints. @files = report_break_on_load; # Normalize for the purposes of our printing this. local $\ = ''; local $" = ' '; print $OUT "Will stop on load of '@files'.\n"; } ## end sub cmd_b_load use vars qw($filename_error); $filename_error = ''; sub breakable_line { my ( $from, $to ) = @_; # $i is the start point. (Where are the FORTRAN programs of yesteryear?) my $i = $from; # If there are at least 2 arguments, we're trying to search a range. if ( @_ >= 2 ) { # $delta is positive for a forward search, negative for a backward one. my $delta = $from < $to ? +1 : -1; # Keep us from running off the ends of the file. my $limit = $delta > 0 ? $#dbline : 1; # Clever test. If you're a mathematician, it's obvious why this # test works. If not: # If $delta is positive (going forward), $limit will be $#dbline. # If $to is less than $limit, ($limit - $to) will be positive, times # $delta of 1 (positive), so the result is > 0 and we should use $to # as the stopping point. # # If $to is greater than $limit, ($limit - $to) is negative, # times $delta of 1 (positive), so the result is < 0 and we should # use $limit ($#dbline) as the stopping point. # # If $delta is negative (going backward), $limit will be 1. # If $to is zero, ($limit - $to) will be 1, times $delta of -1 # (negative) so the result is > 0, and we use $to as the stopping # point. # # If $to is less than zero, ($limit - $to) will be positive, # times $delta of -1 (negative), so the result is not > 0, and # we use $limit (1) as the stopping point. # # If $to is 1, ($limit - $to) will zero, times $delta of -1 # (negative), still giving zero; the result is not > 0, and # we use $limit (1) as the stopping point. # # if $to is >1, ($limit - $to) will be negative, times $delta of -1 # (negative), giving a positive (>0) value, so we'll set $limit to # $to. $limit = $to if ( $limit - $to ) * $delta > 0; # The real search loop. # $i starts at $from (the point we want to start searching from). # We move through @dbline in the appropriate direction (determined # by $delta: either -1 (back) or +1 (ahead). # We stay in as long as we haven't hit an executable line # ($dbline[$i] == 0 means not executable) and we haven't reached # the limit yet (test similar to the above). $i += $delta while $dbline[$i] == 0 and ( $limit - $i ) * $delta > 0; } ## end if (@_ >= 2) # If $i points to a line that is executable, return that. return $i unless $dbline[$i] == 0; # Format the message and print it: no breakable lines in range. my ( $pl, $upto ) = ( '', '' ); ( $pl, $upto ) = ( 's', "..$to" ) if @_ >= 2 and $from != $to; # If there's a filename in filename_error, we'll see it. # If not, not. die "Line$pl $from$upto$filename_error not breakable\n"; } ## end sub breakable_line sub breakable_line_in_filename { # Capture the file name. my ($f) = shift; # Swap the magic line array over there temporarily. local *dbline = $main::{ '_<' . $f }; # If there's an error, it's in this other file. local $filename_error = " of '$f'"; # Find the breakable line. breakable_line(@_); # *dbline and $filename_error get restored when this block ends. } ## end sub breakable_line_in_filename sub break_on_line { my $i = shift; my $cond = @_ ? shift(@_) : 1; my $inii = $i; my $after = ''; my $pl = ''; # Woops, not a breakable line. $filename_error allows us to say # if it was in a different file. die "Line $i$filename_error not breakable.\n" if $dbline[$i] == 0; # Mark this file as having breakpoints in it. $had_breakpoints{$filename} |= 1; # If there is an action or condition here already ... if ( $dbline{$i} ) { # ... swap this condition for the existing one. $dbline{$i} =~ s/^[^\0]*/$cond/; } else { # Nothing here - just add the condition. $dbline{$i} = $cond; _set_breakpoint_enabled_status($filename, $i, 1); } return; } ## end sub break_on_line sub cmd_b_line { if (not eval { break_on_line(@_); 1 }) { local $\ = ''; print $OUT $@ and return; } return; } ## end sub cmd_b_line sub cmd_b_filename_line { if (not eval { break_on_filename_line(@_); 1 }) { local $\ = ''; print $OUT $@ and return; } return; } sub break_on_filename_line { my $f = shift; my $i = shift; my $cond = @_ ? shift(@_) : 1; # Switch the magical hash temporarily. local *dbline = $main::{ '_<' . $f }; # Localize the variables that break_on_line uses to make its message. local $filename_error = " of '$f'"; local $filename = $f; # Add the breakpoint. break_on_line( $i, $cond ); return; } ## end sub break_on_filename_line sub break_on_filename_line_range { my $f = shift; my $from = shift; my $to = shift; my $cond = @_ ? shift(@_) : 1; # Find a breakable line if there is one. my $i = breakable_line_in_filename( $f, $from, $to ); # Add the breakpoint. break_on_filename_line( $f, $i, $cond ); return; } ## end sub break_on_filename_line_range sub subroutine_filename_lines { my ( $subname ) = @_; # Returned value from find_sub() is fullpathname:startline-endline. # The match creates the list (fullpathname, start, end). return (find_sub($subname) =~ /^(.*):(\d+)-(\d+)$/); } ## end sub subroutine_filename_lines sub break_subroutine { my $subname = shift; # Get filename, start, and end. my ( $file, $s, $e ) = subroutine_filename_lines($subname) or die "Subroutine $subname not found.\n"; # Null condition changes to '1' (always true). my $cond = @_ ? shift(@_) : 1; # Put a break the first place possible in the range of lines # that make up this subroutine. break_on_filename_line_range( $file, $s, $e, $cond ); return; } ## end sub break_subroutine sub cmd_b_sub { my $subname = shift; my $cond = @_ ? shift : 1; # If the subname isn't a code reference, qualify it so that # break_subroutine() will work right. if ( ref($subname) ne 'CODE' ) { # Not Perl 4. $subname =~ s/'/::/g; my $s = $subname; # Put it in this package unless it's already qualified. if ($subname !~ /::/) { $subname = $package . '::' . $subname; }; # Requalify it into CORE::GLOBAL if qualifying it into this # package resulted in its not being defined, but only do so # if it really is in CORE::GLOBAL. my $core_name = "CORE::GLOBAL::$s"; if ((!defined(&$subname)) and ($s !~ /::/) and (defined &{$core_name})) { $subname = $core_name; } # Put it in package 'main' if it has a leading ::. if ($subname =~ /\A::/) { $subname = "main" . $subname; } } ## end if ( ref($subname) ne 'CODE' ) { # Try to set the breakpoint. if (not eval { break_subroutine( $subname, $cond ); 1 }) { local $\ = ''; print {$OUT} $@; return; } return; } ## end sub cmd_b_sub sub cmd_B { my $cmd = shift; # No line spec? Use dbline. # If there is one, use it if it's non-zero, or wipe it out if it is. my $line = ( $_[0] =~ /\A\./ ) ? $dbline : (shift || ''); my $dbline = shift; # If the line was dot, make the line the current one. $line =~ s/^\./$dbline/; # If it's * we're deleting all the breakpoints. if ( $line eq '*' ) { if (not eval { delete_breakpoint(); 1 }) { print {$OUT} $@; } } # If there is a line spec, delete the breakpoint on that line. elsif ( $line =~ /\A(\S.*)/ ) { if (not eval { delete_breakpoint( $line || $dbline ); 1 }) { local $\ = ''; print {$OUT} $@; } } ## end elsif ($line =~ /^(\S.*)/) # No line spec. else { print {$OUT} "Deleting a breakpoint requires a line number, or '*' for all\n" ; # hint } return; } ## end sub cmd_B sub _remove_breakpoint_entry { my ($fn, $i) = @_; delete $dbline{$i}; _delete_breakpoint_data_ref($fn, $i); return; } sub _delete_all_breakpoints { print {$OUT} "Deleting all breakpoints...\n"; # %had_breakpoints lists every file that had at least one # breakpoint in it. for my $fn ( keys %had_breakpoints ) { # Switch to the desired file temporarily. local *dbline = $main::{ '_<' . $fn }; $max = $#dbline; # For all lines in this file ... for my $i (1 .. $max) { # If there's a breakpoint or action on this line ... if ( defined $dbline{$i} ) { # ... remove the breakpoint. $dbline{$i} =~ s/\A[^\0]+//; if ( $dbline{$i} =~ s/\A\0?\z// ) { # Remove the entry altogether if no action is there. _remove_breakpoint_entry($fn, $i); } } ## end if (defined $dbline{$i... } ## end for $i (1 .. $max) # If, after we turn off the "there were breakpoints in this file" # bit, the entry in %had_breakpoints for this file is zero, # we should remove this file from the hash. if ( not $had_breakpoints{$fn} &= (~1) ) { delete $had_breakpoints{$fn}; } } ## end for my $fn (keys %had_breakpoints) # Kill off all the other breakpoints that are waiting for files that # haven't been loaded yet. undef %postponed; undef %postponed_file; undef %break_on_load; return; } sub _delete_breakpoint_from_line { my ($i) = @_; # Woops. This line wasn't breakable at all. die "Line $i not breakable.\n" if $dbline[$i] == 0; # Kill the condition, but leave any action. $dbline{$i} =~ s/\A[^\0]*//; # Remove the entry entirely if there's no action left. if ($dbline{$i} eq '') { _remove_breakpoint_entry($filename, $i); } return; } sub delete_breakpoint { my $i = shift; # If we got a line, delete just that one. if ( defined($i) ) { _delete_breakpoint_from_line($i); } # No line; delete them all. else { _delete_all_breakpoints(); } return; } sub cmd_stop { # As on ^C, but not signal-safy. $signal = 1; } sub cmd_e { my $cmd = shift; my $line = shift; unless (exists($INC{'threads.pm'})) { print "threads not loaded($ENV{PERL5DB_THREADED}) please run the debugger with PERL5DB_THREADED=1 set in the environment\n"; } else { my $tid = threads->tid; print "thread id: $tid\n"; } } ## end sub cmd_e sub cmd_E { my $cmd = shift; my $line = shift; unless (exists($INC{'threads.pm'})) { print "threads not loaded($ENV{PERL5DB_THREADED}) please run the debugger with PERL5DB_THREADED=1 set in the environment\n"; } else { my $tid = threads->tid; print "thread ids: ".join(', ', map { ($tid == $_->tid ? '<'.$_->tid.'>' : $_->tid) } threads->list )."\n"; } } ## end sub cmd_E use vars qw($help); use vars qw($summary); sub cmd_h { my $cmd = shift; # If we have no operand, assume null. my $line = shift || ''; # 'h h'. Print the long-format help. if ( $line =~ /\Ah\s*\z/ ) { print_help($help); } # 'h '. Search for the command and print only its help. elsif ( my ($asked) = $line =~ /\A(\S.*)\z/ ) { # support long commands; otherwise bogus errors # happen when you ask for h on for example my $qasked = quotemeta($asked); # for searching; we don't # want to use it as a pattern. # XXX: finds CR but not # Search the help string for the command. if ( $help =~ /^ # Start of a line is not a debugger command.\n"); } } ## end elsif ($line =~ /^(\S.*)$/) # 'h' - print the summary help. else { print_help($summary); } } ## end sub cmd_h sub cmd_i { my $cmd = shift; my $line = shift; foreach my $isa ( split( /\s+/, $line ) ) { $evalarg = $isa; # The &-call is here to ascertain the mutability of @_. ($isa) = &DB::eval; no strict 'refs'; print join( ', ', map { "$_" . ( defined( ${"$_\::VERSION"} ) ? ' ' . ${"$_\::VERSION"} : undef ) } @{mro::get_linear_isa(ref($isa) || $isa)} ); print "\n"; } } ## end sub cmd_i sub _min { my $min = shift; foreach my $v (@_) { if ($min > $v) { $min = $v; } } return $min; } sub _max { my $max = shift; foreach my $v (@_) { if ($max < $v) { $max = $v; } } return $max; } sub _minify_to_max { my $ref = shift; $$ref = _min($$ref, $max); return; } sub _cmd_l_handle_var_name { my $var_name = shift; $evalarg = $var_name; my ($s) = DB::eval(); # Ooops. Bad scalar. if ($@) { print {$OUT} "Error: $@\n"; next CMD; } # Good scalar. If it's a reference, find what it points to. $s = CvGV_name($s); print {$OUT} "Interpreted as: $1 $s\n"; $line = "$1 $s"; # Call self recursively to really do the command. return _cmd_l_main( $s ); } sub _cmd_l_handle_subname { my $s = $subname; # De-Perl4. $subname =~ s/\'/::/; # Put it in this package unless it starts with ::. $subname = $package . "::" . $subname unless $subname =~ /::/; # Put it in CORE::GLOBAL if t doesn't start with :: and # it doesn't live in this package and it lives in CORE::GLOBAL. $subname = "CORE::GLOBAL::$s" if not defined &$subname and $s !~ /::/ and defined &{"CORE::GLOBAL::$s"}; # Put leading '::' names into 'main::'. $subname = "main" . $subname if substr( $subname, 0, 2 ) eq "::"; # Get name:start-stop from find_sub, and break this up at # colons. my @pieces = split( /:/, find_sub($subname) || $sub{$subname} ); # Pull off start-stop. my $subrange = pop @pieces; # If the name contained colons, the split broke it up. # Put it back together. $file = join( ':', @pieces ); # If we're not in that file, switch over to it. if ( $file ne $filename ) { if (! $slave_editor) { print {$OUT} "Switching to file '$file'.\n"; } # Switch debugger's magic structures. *dbline = $main::{ '_<' . $file }; $max = $#dbline; $filename = $file; } ## end if ($file ne $filename) # Subrange is 'start-stop'. If this is less than a window full, # swap it to 'start+', which will list a window from the start point. if ($subrange) { if ( eval($subrange) < -$window ) { $subrange =~ s/-.*/+/; } # Call self recursively to list the range. return _cmd_l_main( $subrange ); } ## end if ($subrange) # Couldn't find it. else { print {$OUT} "Subroutine $subname not found.\n"; return; } } sub _cmd_l_empty { # Compute new range to list. $incr = $window - 1; # Recurse to do it. return _cmd_l_main( $start . '-' . ( $start + $incr ) ); } sub _cmd_l_plus { my ($new_start, $new_incr) = @_; # Don't reset start for 'l +nnn'. $start = $new_start if $new_start; # Increment for list. Use window size if not specified. # (Allows 'l +' to work.) $incr = $new_incr || ($window - 1); # Create a line range we'll understand, and recurse to do it. return _cmd_l_main( $start . '-' . ( $start + $incr ) ); } sub _cmd_l_calc_initial_end_and_i { my ($spec, $start_match, $end_match) = @_; # Determine end point; use end of file if not specified. my $end = ( !defined $start_match ) ? $max : ( $end_match ? $end_match : $start_match ); # Go on to the end, and then stop. _minify_to_max(\$end); # Determine start line. my $i = $start_match; if ($i eq '.') { $i = $spec; } $i = _max($i, 1); $incr = $end - $i; return ($end, $i); } sub _cmd_l_range { my ($spec, $current_line, $start_match, $end_match) = @_; my ($end, $i) = _cmd_l_calc_initial_end_and_i($spec, $start_match, $end_match); # If we're running under a slave editor, force it to show the lines. if ($slave_editor) { print {$OUT} "\032\032$filename:$i:0\n"; $i = $end; } # We're doing it ourselves. We want to show the line and special # markers for: # - the current line in execution # - whether a line is breakable or not # - whether a line has a break or not # - whether a line has an action or not else { I_TO_END: for ( ; $i <= $end ; $i++ ) { # Check for breakpoints and actions. my ( $stop, $action ); if ($dbline{$i}) { ( $stop, $action ) = split( /\0/, $dbline{$i} ); } # ==> if this is the current line in execution, # : if it's breakable. my $arrow = ( $i == $current_line and $filename eq $filename_ini ) ? '==>' : ( $dbline[$i] + 0 ? ':' : ' ' ); # Add break and action indicators. $arrow .= 'b' if $stop; $arrow .= 'a' if $action; # Print the line. print {$OUT} "$i$arrow\t", $dbline[$i]; # Move on to the next line. Drop out on an interrupt. if ($signal) { $i++; last I_TO_END; } } ## end for (; $i <= $end ; $i++) # Line the prompt up; print a newline if the last line listed # didn't have a newline. if ($dbline[ $i - 1 ] !~ /\n\z/) { print {$OUT} "\n"; } } ## end else [ if ($slave_editor) # Save the point we last listed to in case another relative 'l' # command is desired. Don't let it run off the end. $start = $i; _minify_to_max(\$start); return; } sub _cmd_l_main { my $spec = shift; # If this is '-something', delete any spaces after the dash. $spec =~ s/\A-\s*\z/-/; # If the line is '$something', assume this is a scalar containing a # line number. # Set up for DB::eval() - evaluate in *user* context. if ( my ($var_name) = $spec =~ /\A(\$.*)/s ) { return _cmd_l_handle_var_name($var_name); } # l name. Try to find a sub by that name. elsif ( ($subname) = $spec =~ /\A([\':A-Za-z_][\':\w]*(?:\[.*\])?)/s ) { return _cmd_l_handle_subname(); } # Bare 'l' command. elsif ( $spec !~ /\S/ ) { return _cmd_l_empty(); } # l [start]+number_of_lines elsif ( my ($new_start, $new_incr) = $spec =~ /\A(\d*)\+(\d*)\z/ ) { return _cmd_l_plus($new_start, $new_incr); } # l start-stop or l start,stop elsif (my ($s, $e) = $spec =~ /^(?:(-?[\d\$\.]+)(?:[-,]([\d\$\.]+))?)?/ ) { return _cmd_l_range($spec, $line, $s, $e); } return; } ## end sub cmd_l sub cmd_l { my (undef, $line) = @_; return _cmd_l_main($line); } sub _cmd_L_calc_arg { # If no argument, list everything. Pre-5.8.0 version always lists # everything my $arg = shift || 'abw'; if ($CommandSet ne '580') { $arg = 'abw'; } return $arg; } sub _cmd_L_calc_wanted_flags { my $arg = _cmd_L_calc_arg(shift); return (map { index($arg, $_) >= 0 ? 1 : 0 } qw(a b w)); } sub _cmd_L_handle_breakpoints { my ($handle_db_line) = @_; BREAKPOINTS_SCAN: # Look in all the files with breakpoints... for my $file ( keys %had_breakpoints ) { # Temporary switch to this file. local *dbline = $main::{ '_<' . $file }; # Set up to look through the whole file. $max = $#dbline; my $was; # Flag: did we print something # in this file? # For each line in the file ... for my $i (1 .. $max) { # We've got something on this line. if ( defined $dbline{$i} ) { # Print the header if we haven't. if (not $was++) { print {$OUT} "$file:\n"; } # Print the line. print {$OUT} " $i:\t", $dbline[$i]; $handle_db_line->($dbline{$i}); # Quit if the user hit interrupt. if ($signal) { last BREAKPOINTS_SCAN; } } ## end if (defined $dbline{$i... } ## end for my $i (1 .. $max) } ## end for my $file (keys %had_breakpoints) return; } sub _cmd_L_handle_postponed_breakpoints { my ($handle_db_line) = @_; print {$OUT} "Postponed breakpoints in files:\n"; POSTPONED_SCANS: for my $file ( keys %postponed_file ) { my $db = $postponed_file{$file}; print {$OUT} " $file:\n"; for my $line ( sort { $a <=> $b } keys %$db ) { print {$OUT} " $line:\n"; $handle_db_line->($db->{$line}); if ($signal) { last POSTPONED_SCANS; } } if ($signal) { last POSTPONED_SCANS; } } return; } sub cmd_L { my $cmd = shift; my ($action_wanted, $break_wanted, $watch_wanted) = _cmd_L_calc_wanted_flags(shift); my $handle_db_line = sub { my ($l) = @_; my ( $stop, $action ) = split( /\0/, $l ); if ($stop and $break_wanted) { print {$OUT} " break if (", $stop, ")\n" } if ($action && $action_wanted) { print {$OUT} " action: ", $action, "\n" } return; }; # Breaks and actions are found together, so we look in the same place # for both. if ( $break_wanted or $action_wanted ) { _cmd_L_handle_breakpoints($handle_db_line); } # Look for breaks in not-yet-compiled subs: if ( %postponed and $break_wanted ) { print {$OUT} "Postponed breakpoints in subroutines:\n"; my $subname; SUBS_SCAN: for $subname ( keys %postponed ) { print {$OUT} " $subname\t$postponed{$subname}\n"; if ($signal) { last SUBS_SCAN; } } } ## end if (%postponed and $break_wanted) # Find files that have not-yet-loaded breaks: my @have = map { # Combined keys keys %{ $postponed_file{$_} } } keys %postponed_file; # If there are any, list them. if ( @have and ( $break_wanted or $action_wanted ) ) { _cmd_L_handle_postponed_breakpoints($handle_db_line); } ## end if (@have and ($break_wanted... if ( %break_on_load and $break_wanted ) { print {$OUT} "Breakpoints on load:\n"; BREAK_ON_LOAD: for my $filename ( keys %break_on_load ) { print {$OUT} " $filename\n"; last BREAK_ON_LOAD if $signal; } } ## end if (%break_on_load and... if ($watch_wanted and ( $trace & 2 )) { print {$OUT} "Watch-expressions:\n" if @to_watch; TO_WATCH: for my $expr (@to_watch) { print {$OUT} " $expr\n"; last TO_WATCH if $signal; } } return; } ## end sub cmd_L sub cmd_M { list_modules(); return; } sub cmd_o { my $cmd = shift; my $opt = shift || ''; # opt[=val] # Nonblank. Try to parse and process. if ( $opt =~ /^(\S.*)/ ) { parse_options($1); } # Blank. List the current option settings. else { for (@options) { dump_option($_); } } } ## end sub cmd_o sub cmd_O { print $OUT "The old O command is now the o command.\n"; # hint print $OUT "Use 'h' to get current command help synopsis or\n"; # print $OUT "use 'o CommandSet=pre580' to revert to old usage\n"; # } use vars qw($preview); sub cmd_v { my $cmd = shift; my $line = shift; # Extract the line to list around. (Astute readers will have noted that # this pattern will match whether or not a numeric line is specified, # which means that we'll always enter this loop (though a non-numeric # argument results in no action at all)). if ( $line =~ /^(\d*)$/ ) { # Total number of lines to list (a windowful). $incr = $window - 1; # Set the start to the argument given (if there was one). $start = $1 if $1; # Back up by the context amount. $start -= $preview; # Put together a linespec that cmd_l will like. $line = $start . '-' . ( $start + $incr ); # List the lines. cmd_l( 'l', $line ); } ## end if ($line =~ /^(\d*)$/) } ## end sub cmd_v sub _add_watch_expr { my $expr = shift; # ... save it. push @to_watch, $expr; # Parameterize DB::eval and call it to get the expression's value # in the user's context. This version can handle expressions which # return a list value. $evalarg = $expr; # The &-call is here to ascertain the mutability of @_. my ($val) = join( ' ', &DB::eval); $val = ( defined $val ) ? "'$val'" : 'undef'; # Save the current value of the expression. push @old_watch, $val; # We are now watching expressions. $trace |= 2; return; } sub cmd_w { my $cmd = shift; # Null expression if no arguments. my $expr = shift || ''; # If expression is not null ... if ( $expr =~ /\A\S/ ) { _add_watch_expr($expr); } ## end if ($expr =~ /^(\S.*)/) # You have to give one to get one. else { print $OUT "Adding a watch-expression requires an expression\n"; # hint } return; } sub cmd_W { my $cmd = shift; my $expr = shift || ''; # Delete them all. if ( $expr eq '*' ) { # Not watching now. $trace &= ~2; print $OUT "Deleting all watch expressions ...\n"; # And all gone. @to_watch = @old_watch = (); } # Delete one of them. elsif ( $expr =~ /^(\S.*)/ ) { # Where we are in the list. my $i_cnt = 0; # For each expression ... foreach (@to_watch) { my $val = $to_watch[$i_cnt]; # Does this one match the command argument? if ( $val eq $expr ) { # =~ m/^\Q$i$/) { # Yes. Turn it off, and its value too. splice( @to_watch, $i_cnt, 1 ); splice( @old_watch, $i_cnt, 1 ); } $i_cnt++; } ## end foreach (@to_watch) # We don't bother to turn watching off because # a) we don't want to stop calling watchfunction() if it exists # b) foreach over a null list doesn't do anything anyway } ## end elsif ($expr =~ /^(\S.*)/) # No command arguments entered. else { print $OUT "Deleting a watch-expression requires an expression, or '*' for all\n" ; # hint } } ## end sub cmd_W ### END of the API section sub save { # Save eval failure, command failure, extended OS error, output field # separator, input record separator, output record separator and # the warning setting. @saved = ( $@, $!, $^E, $,, $/, $\, $^W ); $, = ""; # output field separator is null string $/ = "\n"; # input record separator is newline $\ = ""; # output record separator is null string $^W = 0; # warnings are off } ## end sub save sub print_lineinfo { # Make the terminal sensible if we're not the primary debugger. resetterm(1) if $LINEINFO eq $OUT and $term_pid != $$; local $\ = ''; local $, = ''; # $LINEINFO may be undef if $noTTY is set or some other issue. if ($LINEINFO) { print {$LINEINFO} @_; } } ## end sub print_lineinfo # The following takes its argument via $evalarg to preserve current @_ sub postponed_sub { # Get the subroutine name. my $subname = shift; # If this is a 'break + if ' ... if ( $postponed{$subname} =~ s/^break\s([+-]?\d+)\s+if\s// ) { # If there's no offset, use '+0'. my $offset = $1 || 0; # find_sub's value is 'fullpath-filename:start-stop'. It's # possible that the filename might have colons in it too. my ( $file, $i ) = ( find_sub($subname) =~ /^(.*):(\d+)-.*$/ ); if ($i) { # We got the start line. Add the offset '+' from # $postponed{subname}. $i += $offset; # Switch to the file this sub is in, temporarily. local *dbline = $main::{ '_<' . $file }; # No warnings, please. local $^W = 0; # != 0 is magical below # This file's got a breakpoint in it. $had_breakpoints{$file} |= 1; # Last line in file. $max = $#dbline; # Search forward until we hit a breakable line or get to # the end of the file. ++$i until $dbline[$i] != 0 or $i >= $max; # Copy the breakpoint in and delete it from %postponed. $dbline{$i} = delete $postponed{$subname}; } ## end if ($i) # find_sub didn't find the sub. else { local $\ = ''; print $OUT "Subroutine $subname not found.\n"; } return; } ## end if ($postponed{$subname... elsif ( $postponed{$subname} eq 'compile' ) { $signal = 1 } #print $OUT "In postponed_sub for '$subname'.\n"; } ## end sub postponed_sub sub postponed { # If there's a break, process it. if ($ImmediateStop) { # Right, we've stopped. Turn it off. $ImmediateStop = 0; # Enter the command loop when DB::DB gets called. $signal = 1; } # If this is a subroutine, let postponed_sub() deal with it. if (ref(\$_[0]) ne 'GLOB') { return postponed_sub(@_); } # Not a subroutine. Deal with the file. local *dbline = shift; my $filename = $dbline; $filename =~ s/^_ $maxtrace; # Get the file name. my $file = $sub[$i]{file}; # Put in a filename header if short is off. $file = $file eq '-e' ? $file : "file '$file'" unless $short; # Get the actual sub's name, and shorten to $maxtrace's requirement. $s = $sub[$i]{'sub'}; $s = ( substr $s, 0, $maxtrace - 3 ) . '...' if length $s > $maxtrace; # Short report uses trimmed file and sub names. if ($short) { my $sub = @_ >= 4 ? $_[3] : $s; print $fh "$sub[$i]{context}=$sub$args from $file:$sub[$i]{line}\n"; } ## end if ($short) # Non-short report includes full names. else { print $fh "$sub[$i]{context} = $s$args" . " called from $file" . " line $sub[$i]{line}\n"; } } ## end for my $i (0 .. $#sub) } ## end sub print_trace sub _dump_trace_calc_saved_single_arg { my ($nothard, $arg) = @_; my $type; if ( not defined $arg ) { # undefined parameter return "undef"; } elsif ( $nothard and tied $arg ) { # tied parameter return "tied"; } elsif ( $nothard and $type = ref $arg ) { # reference return "ref($type)"; } else { # can be stringified local $_ = "$arg"; # Safe to stringify now - should not call f(). # Backslash any single-quotes or backslashes. s/([\'\\])/\\$1/g; # Single-quote it unless it's a number or a colon-separated # name. s/(.*)/'$1'/s unless /^(?: -?[\d.]+ | \*[\w:]* )$/x; # Turn high-bit characters into meta-whatever, and controls into like # '^D'. require 'meta_notation.pm'; $_ = _meta_notation($_) if /[[:^print:]]/a; return $_; } } sub _dump_trace_calc_save_args { my ($nothard) = @_; return [ map { _dump_trace_calc_saved_single_arg($nothard, $_) } @args ]; } sub dump_trace { # How many levels to skip. my $skip = shift; # How many levels to show. (1e9 is a cheap way of saying "all of them"; # it's unlikely that we'll have more than a billion stack frames. If you # do, you've got an awfully big machine...) my $count = shift || 1e9; # We increment skip because caller(1) is the first level *back* from # the current one. Add $skip to the count of frames so we have a # simple stop criterion, counting from $skip to $count+$skip. $skip++; $count += $skip; # These variables are used to capture output from caller(); my ( $p, $file, $line, $sub, $h, $context ); my ( $e, $r, @sub, $args ); # XXX Okay... why'd we do that? my $nothard = not $frame & 8; local $frame = 0; # Do not want to trace this. my $otrace = $trace; $trace = 0; # Start out at the skip count. # If we haven't reached the number of frames requested, and caller() is # still returning something, stay in the loop. (If we pass the requested # number of stack frames, or we run out - caller() returns nothing - we # quit. # Up the stack frame index to go back one more level each time. for ( my $i = $skip ; $i < $count and ( $p, $file, $line, $sub, $h, $context, $e, $r ) = caller($i) ; $i++ ) { # if the sub has args ($h true), make an anonymous array of the # dumped args. my $args = $h ? _dump_trace_calc_save_args($nothard) : undef; # If context is true, this is array (@)context. # If context is false, this is scalar ($) context. # If neither, context isn't defined. (This is apparently a 'can't # happen' trap.) $context = $context ? '@' : ( defined $context ? "\$" : '.' ); # remove trailing newline-whitespace-semicolon-end of line sequence # from the eval text, if any. $e =~ s/\n\s*\;\s*\Z// if $e; # Escape backslashed single-quotes again if necessary. $e =~ s/([\\\'])/\\$1/g if $e; # if the require flag is true, the eval text is from a require. if ($r) { $sub = "require '$e'"; } # if it's false, the eval text is really from an eval. elsif ( defined $r ) { $sub = "eval '$e'"; } # If the sub is '(eval)', this is a block eval, meaning we don't # know what the eval'ed text actually was. elsif ( $sub eq '(eval)' ) { $sub = "eval {...}"; } # Stick the collected information into @sub as an anonymous hash. push( @sub, { context => $context, sub => $sub, args => $args, file => $file, line => $line } ); # Stop processing frames if the user hit control-C. last if $signal; } ## end for ($i = $skip ; $i < ... # Restore the trace value again. $trace = $otrace; @sub; } ## end sub dump_trace sub action { my $action = shift; while ( $action =~ s/\\$// ) { # We have a backslash on the end. Read more. $action .= gets(); } ## end while ($action =~ s/\\$//) # Return the assembled action. $action; } ## end sub action use vars qw($balanced_brace_re); sub unbalanced { # I hate using globals! $balanced_brace_re ||= qr{ ^ \{ (?: (?> [^{}] + ) # Non-parens without backtracking | (??{ $balanced_brace_re }) # Group with matching parens ) * \} $ }x; return $_[0] !~ m/$balanced_brace_re/; } ## end sub unbalanced sub gets { return DB::readline("cont: "); } sub _db_system { # We save, change, then restore STDIN and STDOUT to avoid fork() since # some non-Unix systems can do system() but have problems with fork(). open( SAVEIN, "<&STDIN" ) || _db_warn("Can't save STDIN"); open( SAVEOUT, ">&STDOUT" ) || _db_warn("Can't save STDOUT"); open( STDIN, "<&IN" ) || _db_warn("Can't redirect STDIN"); open( STDOUT, ">&OUT" ) || _db_warn("Can't redirect STDOUT"); # XXX: using csh or tcsh destroys sigint retvals! system(@_); open( STDIN, "<&SAVEIN" ) || _db_warn("Can't restore STDIN"); open( STDOUT, ">&SAVEOUT" ) || _db_warn("Can't restore STDOUT"); close(SAVEIN); close(SAVEOUT); # most of the $? crud was coping with broken cshisms if ( $? >> 8 ) { _db_warn( "(Command exited ", ( $? >> 8 ), ")\n" ); } elsif ($?) { _db_warn( "(Command died of SIG#", ( $? & 127 ), ( ( $? & 128 ) ? " -- core dumped" : "" ), ")", "\n" ); } ## end elsif ($?) return $?; } ## end sub system *system = \&_db_system; use vars qw($ornaments); use vars qw($rl_attribs); sub setterm { # Load Term::Readline, but quietly; don't debug it and don't trace it. local $frame = 0; local $doret = -2; require Term::ReadLine; # If noTTY is set, but we have a TTY name, go ahead and hook up to it. if ($notty) { if ($tty) { my ( $i, $o ) = split $tty, /,/; $o = $i unless defined $o; open( IN, '<', $i ) or die "Cannot open TTY '$i' for read: $!"; open( OUT, '>', $o ) or die "Cannot open TTY '$o' for write: $!"; $IN = \*IN; $OUT = \*OUT; _autoflush($OUT); } ## end if ($tty) # We don't have a TTY - try to find one via Term::Rendezvous. else { require Term::Rendezvous; # See if we have anything to pass to Term::Rendezvous. # Use $HOME/.perldbtty$$ if not. my $rv = $ENV{PERLDB_NOTTY} || "$ENV{HOME}/.perldbtty$$"; # Rendezvous and get the filehandles. my $term_rv = Term::Rendezvous->new( $rv ); $IN = $term_rv->IN; $OUT = $term_rv->OUT; } ## end else [ if ($tty) } ## end if ($notty) # We're a daughter debugger. Try to fork off another TTY. if ( $term_pid eq '-1' ) { # In a TTY with another debugger resetterm(2); } # If we shouldn't use Term::ReadLine, don't. if ( !$rl ) { $term = Term::ReadLine::Stub->new( 'perldb', $IN, $OUT ); } # We're using Term::ReadLine. Get all the attributes for this terminal. else { $term = Term::ReadLine->new( 'perldb', $IN, $OUT ); $rl_attribs = $term->Attribs; $rl_attribs->{basic_word_break_characters} .= '-:+/*,[])}' if defined $rl_attribs->{basic_word_break_characters} and index( $rl_attribs->{basic_word_break_characters}, ":" ) == -1; $rl_attribs->{special_prefixes} = '$@&%'; $rl_attribs->{completer_word_break_characters} .= '$@&%'; $rl_attribs->{completion_function} = \&db_complete; } ## end else [ if (!$rl) # Set up the LINEINFO filehandle. $LINEINFO = $OUT unless defined $LINEINFO; $lineinfo = $console unless defined $lineinfo; $term->MinLine(2); load_hist(); if ( $term->Features->{setHistory} and "@hist" ne "?" ) { $term->SetHistory(@hist); } # XXX Ornaments are turned on unconditionally, which is not # always a good thing. ornaments($ornaments) if defined $ornaments; $term_pid = $$; } ## end sub setterm sub load_hist { $histfile //= option_val("HistFile", undef); return unless defined $histfile; open my $fh, "<", $histfile or return; local $/ = "\n"; @hist = (); while (<$fh>) { chomp; push @hist, $_; } close $fh; } sub save_hist { return unless defined $histfile; eval { require File::Path } or return; eval { require File::Basename } or return; File::Path::mkpath(File::Basename::dirname($histfile)); open my $fh, ">", $histfile or die "Could not open '$histfile': $!"; $histsize //= option_val("HistSize",100); my @copy = grep { $_ ne '?' } @hist; my $start = scalar(@copy) > $histsize ? scalar(@copy)-$histsize : 0; for ($start .. $#copy) { print $fh "$copy[$_]\n"; } close $fh or die "Could not write '$histfile': $!"; } sub connect_remoteport { require IO::Socket; my $socket = IO::Socket::INET->new( Timeout => '10', PeerAddr => $remoteport, Proto => 'tcp', ); if ( ! $socket ) { die "Unable to connect to remote host: $remoteport\n"; } return $socket; } sub socket_get_fork_TTY { $tty = $LINEINFO = $IN = $OUT = connect_remoteport(); # Do I need to worry about setting $term? reset_IN_OUT( $IN, $OUT ); return ''; } sub xterm_get_fork_TTY { ( my $name = $0 ) =~ s,^.*[/\\],,s; open XT, qq[3>&1 xterm -title "Daughter Perl debugger $pids $name" -e sh -c 'tty 1>&3;\ sleep 10000000' |]; # Get the output from 'tty' and clean it up a little. my $tty = ; chomp $tty; $pidprompt = ''; # Shown anyway in titlebar # We need $term defined or we can not switch to the newly created xterm if ($tty ne '' && !defined $term) { require Term::ReadLine; if ( !$rl ) { $term = Term::ReadLine::Stub->new( 'perldb', $IN, $OUT ); } else { $term = Term::ReadLine->new( 'perldb', $IN, $OUT ); } } # There's our new TTY. return $tty; } ## end sub xterm_get_fork_TTY # This example function resets $IN, $OUT itself my $c_pipe = 0; sub os2_get_fork_TTY { # A simplification of the following (and works without): local $\ = ''; ( my $name = $0 ) =~ s,^.*[/\\],,s; my %opt = ( title => "Daughter Perl debugger $pids $name", ($rl ? (read_by_key => 1) : ()) ); require OS2::Process; my ($in, $out, $pid) = eval { OS2::Process::io_term(related => 0, %opt) } or return; $pidprompt = ''; # Shown anyway in titlebar reset_IN_OUT($in, $out); $tty = '*reset*'; return ''; # Indicate that reset_IN_OUT is called } ## end sub os2_get_fork_TTY # Notes about Terminal.app's AppleScript support, # (aka things that might break in future OS versions). # # The "do script" command doesn't return a reference to the new window # it creates, but since it appears frontmost and windows are enumerated # front to back, we can use "first window" === "window 1". # # Since "do script" is implemented by supplying the argument (plus a # return character) as terminal input, there's a potential race condition # where the debugger could beat the shell to reading the command. # To prevent this, we wait for the screen to clear before proceeding. # # 10.3 and 10.4: # There's no direct accessor for the tty device name, so we fiddle # with the window title options until it says what we want. # # 10.5: # There _is_ a direct accessor for the tty device name, _and_ there's # a new possible component of the window title (the name of the settings # set). A separate version is needed. my @script_versions= ([237, <<'__LEOPARD__'], tell application "Terminal" do script "clear;exec sleep 100000" tell first tab of first window copy tty to thetty set custom title to "forked perl debugger" set title displays custom title to true repeat while (length of first paragraph of (get contents)) > 0 delay 0.1 end repeat end tell end tell thetty __LEOPARD__ [100, <<'__JAGUAR_TIGER__'], tell application "Terminal" do script "clear;exec sleep 100000" tell first window set title displays shell path to false set title displays window size to false set title displays file name to false set title displays device name to true set title displays custom title to true set custom title to "" copy "/dev/" & name to thetty set custom title to "forked perl debugger" repeat while (length of first paragraph of (get contents)) > 0 delay 0.1 end repeat end tell end tell thetty __JAGUAR_TIGER__ ); sub macosx_get_fork_TTY { my($version,$script,$pipe,$tty); return unless $version=$ENV{TERM_PROGRAM_VERSION}; foreach my $entry (@script_versions) { if ($version>=$entry->[0]) { $script=$entry->[1]; last; } } return unless defined($script); return unless open($pipe,'-|','/usr/bin/osascript','-e',$script); $tty=readline($pipe); close($pipe); return unless defined($tty) && $tty =~ m(^/dev/); chomp $tty; return $tty; } sub tmux_get_fork_TTY { return unless $ENV{TMUX}; my $pipe; my $status = open $pipe, '-|', 'tmux', 'split-window', '-P', '-F', '#{pane_tty}', 'sleep 100000'; if ( !$status ) { return; } my $tty = <$pipe>; close $pipe; if ( $tty ) { chomp $tty; if ( !defined $term ) { require Term::ReadLine; if ( !$rl ) { $term = Term::ReadLine::Stub->new( 'perldb', $IN, $OUT ); } else { $term = Term::ReadLine->new( 'perldb', $IN, $OUT ); } } } return $tty; } use vars qw($fork_TTY); sub create_IN_OUT { # Create a window with IN/OUT handles redirected there # If we know how to get a new TTY, do it! $in will have # the TTY name if get_fork_TTY works. my $in = get_fork_TTY(@_) if defined &get_fork_TTY; # It used to be that $in = $fork_TTY if defined $fork_TTY; # Backward compatibility if ( not defined $in ) { my $why = shift; # We don't know how. print_help(< Forked, but do not know how to create a new B. I<#########> EOP # Forked debugger. print_help(< Daughter session, do not know how to change a B. I<#########> This may be an asynchronous session, so the parent debugger may be active. EOP # Note that both debuggers are fighting over the same input. print_help(< in B<\$DB::fork_TTY>, or define a function B returning this. On I-like systems one can get the name of a I for the given window by typing B, and disconnect the I from I by B. EOP } ## end if (not defined $in) elsif ( $in ne '' ) { TTY($in); } else { $console = ''; # Indicate no need to open-from-the-console } undef $fork_TTY; } ## end sub create_IN_OUT sub resetterm { # We forked, so we need a different TTY # Needs to be passed to create_IN_OUT() as well. my $in = shift; # resetterm(2): got in here because of a system() starting a debugger. # resetterm(1): just forked. my $systemed = $in > 1 ? '-' : ''; # If there's already a list of pids, add this to the end. if ($pids) { $pids =~ s/\]/$systemed->$$]/; } # No pid list. Time to make one. else { $pids = "[$term_pid->$$]"; } # The prompt we're going to be using for this debugger. $pidprompt = $pids; # We now 0wnz this terminal. $term_pid = $$; # Just return if we're not supposed to try to create a new TTY. return unless $CreateTTY & $in; # Try to create a new IN/OUT pair. create_IN_OUT($in); } ## end sub resetterm sub readline { # Localize to prevent it from being smashed in the program being debugged. local $.; # If there are stacked filehandles to read from ... # (Handle it before the typeahead, because we may call source/etc. from # the typeahead.) while (@cmdfhs) { # Read from the last one in the stack. my $line = CORE::readline( $cmdfhs[-1] ); # If we got a line ... defined $line ? ( print $OUT ">> $line" and return $line ) # Echo and return : close pop @cmdfhs; # Pop and close } ## end while (@cmdfhs) # Pull a line out of the typeahead if there's stuff there. if (@typeahead) { # How many lines left. my $left = @typeahead; # Get the next line. my $got = shift @typeahead; # Print a message saying we got input from the typeahead. local $\ = ''; print $OUT "auto(-$left)", shift, $got, "\n"; # Add it to the terminal history (if possible). $term->AddHistory($got) if length($got) > 1 and defined $term->Features->{addHistory}; return $got; } ## end if (@typeahead) # We really need to read some input. Turn off entry/exit trace and # return value printing. local $frame = 0; local $doret = -2; # Nothing on the filehandle stack. Socket? if ( ref $OUT and UNIVERSAL::isa( $OUT, 'IO::Socket::INET' ) ) { # Send anything we have to send. $OUT->write( join( '', @_ ) ); # Receive anything there is to receive. my $stuff = ''; my $buf; my $first_time = 1; while ($first_time or (length($buf) && ($stuff .= $buf) !~ /\n/)) { $first_time = 0; $IN->recv( $buf = '', 2048 ); # XXX "what's wrong with sysread?" # XXX Don't know. You tell me. } # What we got. return $stuff; } ## end if (ref $OUT and UNIVERSAL::isa... # No socket. Just read from the terminal. else { return $term->readline(@_); } } ## end sub readline sub dump_option { my ( $opt, $val ) = @_; $val = option_val( $opt, 'N/A' ); $val =~ s/([\\\'])/\\$1/g; printf $OUT "%20s = '%s'\n", $opt, $val; } ## end sub dump_option sub options2remember { foreach my $k (@RememberOnROptions) { $option{$k} = option_val( $k, 'N/A' ); } return %option; } sub option_val { my ( $opt, $default ) = @_; my $val; # Does this option exist, and is it a variable? # If so, retrieve the value via the value in %optionVars. if ( defined $optionVars{$opt} and defined ${ $optionVars{$opt} } ) { $val = ${ $optionVars{$opt} }; } # Does this option exist, and it's a subroutine? # If so, call the subroutine via the ref in %optionAction # and capture the value. elsif ( defined $optionAction{$opt} and defined &{ $optionAction{$opt} } ) { $val = &{ $optionAction{$opt} }(); } # If there's an action or variable for the supplied option, # but no value was set, use the default. elsif (defined $optionAction{$opt} and not defined $option{$opt} or defined $optionVars{$opt} and not defined ${ $optionVars{$opt} } ) { $val = $default; } # Otherwise, do the simple hash lookup. else { $val = $option{$opt}; } # If the value isn't defined, use the default. # Then return whatever the value is. $val = $default unless defined $val; $val; } ## end sub option_val sub parse_options { my ($s) = @_; local $\ = ''; my $option; # These options need a value. Don't allow them to be clobbered by accident. my %opt_needs_val = map { ( $_ => 1 ) } qw{ dumpDepth arrayDepth hashDepth LineInfo maxTraceLen ornaments windowSize pager quote ReadLine recallCommand RemotePort ShellBang TTY CommandSet }; while (length($s)) { my $val_defaulted; # Clean off excess leading whitespace. $s =~ s/^\s+// && next; # Options are always all word characters, followed by a non-word # separator. if ($s !~ s/^(\w+)(\W?)//) { print {$OUT} "Invalid option '$s'\n"; last; } my ( $opt, $sep ) = ( $1, $2 ); # Make sure that such an option exists. my $matches = ( grep { /^\Q$opt/ && ( $option = $_ ) } @options ) || ( grep { /^\Q$opt/i && ( $option = $_ ) } @options ); unless ($matches) { print {$OUT} "Unknown option '$opt'\n"; next; } if ($matches > 1) { print {$OUT} "Ambiguous option '$opt'\n"; next; } my $val; # '?' as separator means query, but must have whitespace after it. if ( "?" eq $sep ) { if ($s =~ /\A\S/) { print {$OUT} "Option query '$opt?' followed by non-space '$s'\n" ; last; } #&dump_option($opt); } ## end if ("?" eq $sep) # Separator is whitespace (or just a carriage return). # They're going for a default, which we assume is 1. elsif ( $sep !~ /\S/ ) { $val_defaulted = 1; $val = "1"; # this is an evil default; make 'em set it! } # Separator is =. Trying to set a value. elsif ( $sep eq "=" ) { # If quoted, extract a quoted string. if ($s =~ s/ (["']) ( (?: \\. | (?! \1 ) [^\\] )* ) \1 //x) { my $quote = $1; ( $val = $2 ) =~ s/\\([$quote\\])/$1/g; } # Not quoted. Use the whole thing. Warn about 'option='. else { $s =~ s/^(\S*)//; $val = $1; print OUT qq(Option better cleared using $opt=""\n) unless length $val; } ## end else [ if (s/ (["']) ( (?: \\. | (?! \1 ) [^\\] )* ) \1 //x) } ## end elsif ($sep eq "=") # "Quoted" with [], <>, or {}. else { #{ to "let some poor schmuck bounce on the % key in B." my ($end) = "\\" . substr( ")]>}$sep", index( "([<{", $sep ), 1 ); #} $s =~ s/^(([^\\$end]|\\[\\$end])*)$end($|\s+)// or print( $OUT "Unclosed option value '$opt$sep$_'\n" ), last; ( $val = $1 ) =~ s/\\([\\$end])/$1/g; } ## end else [ if ("?" eq $sep) # Exclude non-booleans from getting set to 1 by default. if ( $opt_needs_val{$option} && $val_defaulted ) { my $cmd = ( $CommandSet eq '580' ) ? 'o' : 'O'; print {$OUT} "Option '$opt' is non-boolean. Use '$cmd $option=VAL' to set, '$cmd $option?' to query\n"; next; } ## end if ($opt_needs_val{$option... # Save the option value. $option{$option} = $val if defined $val; # Load any module that this option requires. if ( defined($optionRequire{$option}) && defined($val) ) { eval qq{ local \$frame = 0; local \$doret = -2; require '$optionRequire{$option}'; 1; } || die $@ # XXX: shouldn't happen } # Set it. # Stick it in the proper variable if it goes in a variable. if (defined($optionVars{$option}) && defined($val)) { ${ $optionVars{$option} } = $val; } # Call the appropriate sub if it gets set via sub. if (defined($optionAction{$option}) && defined (&{ $optionAction{$option} }) && defined ($val)) { &{ $optionAction{$option} }($val); } # Not initialization - echo the value we set it to. dump_option($option) if ($OUT ne \*STDERR); } ## end while (length) } ## end sub parse_options sub set_list { my ( $stem, @list ) = @_; my $val; # VAR_n: how many we have. Scalar assignment gets the number of items. $ENV{"${stem}_n"} = @list; # Grab each item in the list, escape the backslashes, encode the non-ASCII # as hex, and then save in the appropriate VAR_0, VAR_1, etc. for my $i ( 0 .. $#list ) { $val = $list[$i]; $val =~ s/\\/\\\\/g; no warnings 'experimental::regex_sets'; $val =~ s/ ( (?[ [\000-\xFF] & [:^print:] ]) ) / "\\0x" . unpack('H2',$1)/xaeg; $ENV{"${stem}_$i"} = $val; } ## end for $i (0 .. $#list) } ## end sub set_list sub get_list { my $stem = shift; my @list; my $n = delete $ENV{"${stem}_n"}; my $val; for my $i ( 0 .. $n - 1 ) { $val = delete $ENV{"${stem}_$i"}; $val =~ s/\\((\\)|0x(..))/ $2 ? $2 : pack('H2', $3) /ge; push @list, $val; } @list; } ## end sub get_list sub catch { $signal = 1; return; # Put nothing on the stack - malloc/free land! } sub _db_warn { my ($msg) = join( "", @_ ); $msg .= ": $!\n" unless $msg =~ /\n$/; local $\ = ''; print $OUT $msg; } ## end sub warn *warn = \&_db_warn; sub reset_IN_OUT { my $switch_li = $LINEINFO eq $OUT; # If there's a term and it's able to get a new tty, try to get one. if ( $term and $term->Features->{newTTY} ) { ( $IN, $OUT ) = ( shift, shift ); $term->newTTY( $IN, $OUT ); } # This term can't get a new tty now. Better luck later. elsif ($term) { _db_warn("Too late to set IN/OUT filehandles, enabled on next 'R'!\n"); } # Set the filehndles up as they were. else { ( $IN, $OUT ) = ( shift, shift ); } # Unbuffer the output filehandle. _autoflush($OUT); # Point LINEINFO to the same output filehandle if it was there before. $LINEINFO = $OUT if $switch_li; } ## end sub reset_IN_OUT sub TTY { if ( @_ and $term and $term->Features->{newTTY} ) { # This terminal supports switching to a new TTY. # Can be a list of two files, or on string containing both names, # comma-separated. # XXX Should this perhaps be an assignment from @_? my ( $in, $out ) = shift; if ( $in =~ /,/ ) { # Split list apart if supplied. ( $in, $out ) = split /,/, $in, 2; } else { # Use the same file for both input and output. $out = $in; } # Open file onto the debugger's filehandles, if you can. open IN, '<', $in or die "cannot open '$in' for read: $!"; open OUT, '>', $out or die "cannot open '$out' for write: $!"; # Swap to the new filehandles. reset_IN_OUT( \*IN, \*OUT ); # Save the setting for later. return $tty = $in; } ## end if (@_ and $term and $term... # Terminal doesn't support new TTY, or doesn't support readline. # Can't do it now, try restarting. if ($term and @_) { _db_warn("Too late to set TTY, enabled on next 'R'!\n"); } # Useful if done through PERLDB_OPTS: $console = $tty = shift if @_; # Return whatever the TTY is. $tty or $console; } ## end sub TTY sub noTTY { if ($term) { _db_warn("Too late to set noTTY, enabled on next 'R'!\n") if @_; } $notty = shift if @_; $notty; } ## end sub noTTY sub ReadLine { if ($term) { _db_warn("Too late to set ReadLine, enabled on next 'R'!\n") if @_; } $rl = shift if @_; $rl; } ## end sub ReadLine sub RemotePort { if ($term) { _db_warn("Too late to set RemotePort, enabled on next 'R'!\n") if @_; } $remoteport = shift if @_; $remoteport; } ## end sub RemotePort sub tkRunning { if ( ${ $term->Features }{tkRunning} ) { return $term->tkRunning(@_); } else { local $\ = ''; print $OUT "tkRunning not supported by current ReadLine package.\n"; 0; } } ## end sub tkRunning sub NonStop { if ($term) { _db_warn("Too late to set up NonStop mode, enabled on next 'R'!\n") if @_; } $runnonstop = shift if @_; $runnonstop; } ## end sub NonStop sub DollarCaretP { if ($term) { _db_warn("Some flag changes could not take effect until next 'R'!\n") if @_; } $^P = parse_DollarCaretP_flags(shift) if @_; expand_DollarCaretP_flags($^P); } sub pager { if (@_) { $pager = shift; $pager = "|" . $pager unless $pager =~ /^(\+?\>|\|)/; } $pager; } ## end sub pager sub shellBang { # If we got an argument, meta-quote it, and add '\b' if it # ends in a word character. if (@_) { $sh = quotemeta shift; $sh .= "\\b" if $sh =~ /\w$/; } # Generate the printable version for the help: $psh = $sh; # copy it $psh =~ s/\\b$//; # Take off trailing \b if any $psh =~ s/\\(.)/$1/g; # De-escape $psh; # return the printable version } ## end sub shellBang sub ornaments { if ( defined $term ) { # We don't want to show warning backtraces, but we do want die() ones. local $warnLevel = 0; local $dieLevel = 1; # No ornaments if the terminal doesn't support them. if (not $term->Features->{ornaments}) { return ''; } return (eval { $term->ornaments(@_) } || ''); } # Use what was passed in if we can't determine it ourselves. else { $ornaments = shift; return $ornaments; } } ## end sub ornaments sub recallCommand { # If there is input, metaquote it. Add '\b' if it ends with a word # character. if (@_) { $rc = quotemeta shift; $rc .= "\\b" if $rc =~ /\w$/; } # Build it into a printable version. $prc = $rc; # Copy it $prc =~ s/\\b$//; # Remove trailing \b $prc =~ s/\\(.)/$1/g; # Remove escapes return $prc; # Return the printable version } ## end sub recallCommand sub LineInfo { if (@_) { $lineinfo = shift; # If this is a valid "thing to be opened for output", tack a # '>' onto the front. my $stream = ( $lineinfo =~ /^(\+?\>|\|)/ ) ? $lineinfo : ">$lineinfo"; # If this is a pipe, the stream points to a slave editor. $slave_editor = ( $stream =~ /^\|/ ); my $new_lineinfo_fh; # Open it up and unbuffer it. open ($new_lineinfo_fh , $stream ) or _db_warn("Cannot open '$stream' for write"); $LINEINFO = $new_lineinfo_fh; _autoflush($LINEINFO); } return $lineinfo; } ## end sub LineInfo sub list_modules { # versions my %version; my $file; # keys are the "as-loaded" name, values are the fully-qualified path # to the file itself. for ( keys %INC ) { $file = $_; # get the module name s,\.p[lm]$,,i; # remove '.pl' or '.pm' s,/,::,g; # change '/' to '::' s/^perl5db$/DB/; # Special case: debugger # moves to package DB s/^Term::ReadLine::readline$/readline/; # simplify readline # If the package has a $VERSION package global (as all good packages # should!) decode it and save as partial message. my $pkg_version = do { no strict 'refs'; ${ $_ . '::VERSION' } }; if ( defined $pkg_version ) { $version{$file} = "$pkg_version from "; } # Finish up the message with the file the package came from. $version{$file} .= $INC{$file}; } ## end for (keys %INC) # Hey, dumpit() formats a hash nicely, so why not use it? dumpit( $OUT, \%version ); } ## end sub list_modules use vars qw($pre580_help); use vars qw($pre580_summary); sub sethelp { # XXX: make sure there are tabs between the command and explanation, # or print_help will screw up your formatting if you have # eeevil ornaments enabled. This is an insane mess. $help = " Help is currently only available for the new 5.8 command set. No help is available for the old command set. We assume you know what you're doing if you switch to it. B Stack trace. B [I] Single step [in I]. B [I] Next, steps over subroutine calls [in I]. > Repeat last B or B command. B Return from current subroutine. B [I|I] Continue; optionally inserts a one-time-only breakpoint at the specified position. B IB<+>I List I+1 lines starting at I. B IB<->I List lines I through I. B I List single I. B I List first window of lines from subroutine. B I<\$var> List first window of lines from subroutine referenced by I<\$var>. B List next window of lines. B<-> List previous window of lines. B [I] View window around I. B<.> Return to the executed line. B I Switch to viewing I. File must be already loaded. I may be either the full name of the file, or a regular expression matching the full file name: B I and B I may access the same file. Evals (with saved bodies) are considered to be filenames: B I<(eval 7)> and B I access the body of the 7th eval (in the order of execution). BIB Search forwards for I; final B is optional. BIB Search backwards for I; final B is optional. B [I] List actions and or breakpoints and or watch-expressions. B [[B]I] List subroutine names [not] matching I. B [I] Toggle trace mode (to max I levels below current stack depth). B [I] I Trace through execution of I. B Sets breakpoint on current line) B [I] [I] Set breakpoint; I defaults to the current execution line; I breaks if it evaluates to true, defaults to '1'. B I [I] Set breakpoint at first line of subroutine. B I<\$var> Set breakpoint at first line of subroutine referenced by I<\$var>. B B I Set breakpoint on 'require'ing the given file. B B I [I] Set breakpoint at first line of subroutine after it is compiled. B B I Stop after the subroutine is compiled. B [I] Delete the breakpoint for I. B I<*> Delete all breakpoints. B [I] I Set an action to be done before the I is executed; I defaults to the current execution line. Sequence is: check for breakpoint/watchpoint, print line if necessary, do action, prompt user if necessary, execute line. B Does nothing B [I] Delete the action for I. B I<*> Delete all actions. B I Add a global watch-expression. B Does nothing B I Delete a global watch-expression. B I<*> Delete all watch-expressions. B [I [I]] List some (default all) variables in package (default current). Use B<~>I and BI for positive and negative regexps. B [I] Same as \"B I [I]\". B I Evals expression in list context, dumps the result. B I Evals expression in list context, prints methods callable on the first element of the result. B I Prints methods callable via the given class. B Show versions of loaded modules. B I Prints nested parents of given class. B Display current thread id. B Display all thread ids the current one will be identified: . B [I [I]] List lexicals in higher scope . Vars same as B. B<<> ? List Perl commands to run before each prompt. B<<> I Define Perl command to run before each prompt. B<<<> I Add to the list of Perl commands to run before each prompt. B<< *> Delete the list of perl commands to run before each prompt. B<>> ? List Perl commands to run after each prompt. B<>> I Define Perl command to run after each prompt. B<>>B<>> I Add to the list of Perl commands to run after each prompt. B<>>B< *> Delete the list of Perl commands to run after each prompt. B<{> I Define debugger command to run before each prompt. B<{> ? List debugger commands to run before each prompt. B<{{> I Add to the list of debugger commands to run before each prompt. B<{ *> Delete the list of debugger commands to run before each prompt. B<$prc> I Redo a previous command (default previous command). B<$prc> I<-number> Redo number'th-to-last command. B<$prc> I Redo last command that started with I. See 'B I' too. B<$psh$psh> I Run cmd in a subprocess (reads from DB::IN, writes to DB::OUT)" . ( $rc eq $sh ? "" : " B<$psh> [I] Run I in subshell (forces \"\$SHELL -c 'cmd'\")." ) . " See 'B I' too. B I Execute I containing debugger commands (may nest). B I Save current debugger session (actual history) to I. B Rerun session to current position. B I Rerun session to numbered command. B I<-n> Rerun session to number'th-to-last command. B I<-number> Display last number commands (default all). B I<*> Delete complete history. B

I Print expression (uses script's current package). B [[B]I] List subroutine names [not] matching pattern B [I [I]] List Variables in Package. Vars can be ~pattern or !pattern. B [I] Same as \"B I [I]\". B I inheritance tree. B [I [I]] List lexicals in higher scope . Vars same as B. B Display thread id B Display all thread ids. For more help, type B I, or run B<$doccmd perldebug> for all docs. END_SUM # ')}}; # Fix balance of vi % matching # and this is really numb... $pre580_help = " B Stack trace. B [I] Single step [in I]. B [I] Next, steps over subroutine calls [in I]. B> Repeat last B or B command. B Return from current subroutine. B [I|I] Continue; optionally inserts a one-time-only breakpoint at the specified position. B IB<+>I List I+1 lines starting at I. B IB<->I List lines I through I. B I List single I. B I List first window of lines from subroutine. B I<\$var> List first window of lines from subroutine referenced by I<\$var>. B List next window of lines. B<-> List previous window of lines. B [I] List window around I. B<.> Return to the executed line. B I Switch to viewing I. File must be already loaded. I may be either the full name of the file, or a regular expression matching the full file name: B I and B I may access the same file. Evals (with saved bodies) are considered to be filenames: B I<(eval 7)> and B I access the body of the 7th eval (in the order of execution). BIB Search forwards for I; final B is optional. BIB Search backwards for I; final B is optional. B List all breakpoints and actions. B [[B]I] List subroutine names [not] matching I. B [I] Toggle trace mode (to max I levels below current stack depth) . B [I] I Trace through execution of I. B [I] [I] Set breakpoint; I defaults to the current execution line; I breaks if it evaluates to true, defaults to '1'. B I [I] Set breakpoint at first line of subroutine. B I<\$var> Set breakpoint at first line of subroutine referenced by I<\$var>. B B I Set breakpoint on 'require'ing the given file. B B I [I] Set breakpoint at first line of subroutine after it is compiled. B B I Stop after the subroutine is compiled. B [I] Delete the breakpoint for I. B Delete all breakpoints. B [I] I Set an action to be done before the I is executed; I defaults to the current execution line. Sequence is: check for breakpoint/watchpoint, print line if necessary, do action, prompt user if necessary, execute line. B [I] Delete the action for I. B Delete all actions. B I Add a global watch-expression. B Delete all watch-expressions. B [I [I]] List some (default all) variables in package (default current). Use B<~>I and BI for positive and negative regexps. B [I] Same as \"B I [I]\". B I Evals expression in list context, dumps the result. B I Evals expression in list context, prints methods callable on the first element of the result. B I Prints methods callable via the given class. B<<> ? List Perl commands to run before each prompt. B<<> I Define Perl command to run before each prompt. B<<<> I Add to the list of Perl commands to run before each prompt. B<>> ? List Perl commands to run after each prompt. B<>> I Define Perl command to run after each prompt. B<>>B<>> I Add to the list of Perl commands to run after each prompt. B<{> I Define debugger command to run before each prompt. B<{> ? List debugger commands to run before each prompt. B<{{> I Add to the list of debugger commands to run before each prompt. B<$prc> I Redo a previous command (default previous command). B<$prc> I<-number> Redo number'th-to-last command. B<$prc> I Redo last command that started with I. See 'B I' too. B<$psh$psh> I Run cmd in a subprocess (reads from DB::IN, writes to DB::OUT)" . ( $rc eq $sh ? "" : " B<$psh> [I] Run I in subshell (forces \"\$SHELL -c 'cmd'\")." ) . " See 'B I' too. B I Execute I containing debugger commands (may nest). B I<-number> Display last number commands (default all). B

I Same as \"I\" in current package. B<|>I Run debugger command, piping DB::OUT to current pager. B<||>I Same as B<|>I but DB::OUT is temporarilly select()ed as well. B<\=> [I I] Define a command alias, or list current aliases. I Execute as a perl statement in current package. B Show versions of loaded modules. B Pure-man-restart of debugger, some of debugger state and command-line options may be lost. Currently the following settings are preserved: history, breakpoints and actions, debugger Bptions and the following command-line options: I<-w>, I<-I>, I<-e>. B [I] ... Set boolean option to true B [IB] Query options B [IB<=>I] [I=B<\">IB<\">] ... Set options. Use quotes if spaces in value. I, I chars used to recall command or spawn shell; I program for output of \"|cmd\"; I run Tk while prompting (with ReadLine); I I I level of verbosity; I Allows stepping off the end of the script. I Debugger should stop as early as possible. I Remote hostname:port for remote debugging The following options affect what happens with B, B, and B commands: I, I print only first N elements ('' for all); I, I change style of array and hash dump; I whether to print contents of globs; I dump arrays holding debugged files; I dump symbol tables of packages; I dump contents of \"reused\" addresses; I, I, I change style of string dump; I Do not print the overload-stringified value; Other options include: I affects printing of return value after B command, I affects printing messages on subroutine entry/exit. I affects printing messages on possible breaking points. I gives max length of evals/args listed in stack trace. I affects screen appearance of the command line. I bits control attempts to create a new TTY on events: 1: on fork() 2: debugger is started inside debugger 4: on startup During startup options are initialized from \$ENV{PERLDB_OPTS}. You can put additional initialization options I, I, I, I, and I there (or use B after you set them). B or B<^D> Quit. Set B<\$DB::finished = 0> to debug global destruction. B [I] Get help [on a specific debugger command], enter B<|h> to page. B Summary of debugger commands. B<$doccmd> I Runs the external doc viewer B<$doccmd> command on the named Perl I, or on B<$doccmd> itself if omitted. Set B<\$DB::doccmd> to change viewer. Type '|h' for a paged display if this was too hard to read. "; # Fix balance of vi % matching: }}}} # note: tabs in the following section are not-so-helpful $pre580_summary = <<"END_SUM"; I I B [I|I] List source code B Stack trace B<-> or B<.> List previous/current line B [I] Single step [in expr] B [I] List around line B [I] Next, steps over subs B I View source in file /B> Repeat last B or B BIB BIB Search forw/backw B Return from subroutine B Show versions of modules B [I|I] Continue until position I B List break/watch/actions B [...] Set debugger options B [I] Toggle trace [trace expr] B<<>[B<<>]|B<{>[B<{>]|B<>>[B<>>] [I] Do pre/post-prompt B [I|I|I] [I] Set breakpoint B<$prc> [I|I] Redo a previous command B [I] or B Delete a/all breakpoints B [I<-num>] Display last num commands B [I] I Do cmd before line B<=> [I I] Define/list an alias B I Add a watch expression B [I] Get help on command B or B Delete all actions/watch B<|>[B<|>]I Send output to pager B<$psh>\[B<$psh>\] I Run cmd in a subprocess B or B<^D> Quit B Attempt a restart I B Execute perl code, also see: B,B,B I B|B I Evals expr in list context, dumps the result or lists methods. B

I Print expression (uses script's current package). B [[B]I] List subroutine names [not] matching pattern B [I [I]] List Variables in Package. Vars can be ~pattern or !pattern. B [I] Same as \"B I [I]\". B [I [I]] List lexicals in higher scope . Vars same as B. For more help, type B I, or run B<$doccmd perldebug> for all docs. END_SUM # ')}}; # Fix balance of vi % matching } ## end sub sethelp sub print_help { my $help_str = shift; # Restore proper alignment destroyed by eeevil I<> and B<> # ornaments: A pox on both their houses! # # A help command will have everything up to and including # the first tab sequence padded into a field 16 (or if indented 20) # wide. If it's wider than that, an extra space will be added. $help_str =~ s{ ^ # only matters at start of line ( \ {4} | \t )* # some subcommands are indented ( < ? # so works [BI] < [^\t\n] + ) # find an eeevil ornament ( \t+ ) # original separation, discarded ( .* ) # this will now start (no earlier) than # column 16 } { my($leadwhite, $command, $midwhite, $text) = ($1, $2, $3, $4); my $clean = $command; $clean =~ s/[BI]<([^>]*)>/$1/g; # replace with this whole string: ($leadwhite ? " " x 4 : "") . $command . ((" " x (16 + ($leadwhite ? 4 : 0) - length($clean))) || " ") . $text; }mgex; $help_str =~ s{ # handle bold ornaments B < ( [^>] + | > ) > } { $Term::ReadLine::TermCap::rl_term_set[2] . $1 . $Term::ReadLine::TermCap::rl_term_set[3] }gex; $help_str =~ s{ # handle italic ornaments I < ( [^>] + | > ) > } { $Term::ReadLine::TermCap::rl_term_set[0] . $1 . $Term::ReadLine::TermCap::rl_term_set[1] }gex; local $\ = ''; print {$OUT} $help_str; return; } ## end sub print_help use vars qw($fixed_less); sub _calc_is_less { if ($pager =~ /\bless\b/) { return 1; } elsif ($pager =~ /\bmore\b/) { # Nope, set to more. See what's out there. my @st_more = stat('/usr/bin/more'); my @st_less = stat('/usr/bin/less'); # is it really less, pretending to be more? return ( @st_more && @st_less && $st_more[0] == $st_less[0] && $st_more[1] == $st_less[1] ); } else { return; } } sub fix_less { # We already know if this is set. return if $fixed_less; # changes environment! # 'r' added so we don't do (slow) stats again. $fixed_less = 1 if _calc_is_less(); return; } ## end sub fix_less sub diesignal { # No entry/exit messages. local $frame = 0; # No return value prints. local $doret = -2; # set the abort signal handling to the default (just terminate). $SIG{'ABRT'} = 'DEFAULT'; # If we enter the signal handler recursively, kill myself with an # abort signal (so we just terminate). kill 'ABRT', $$ if $panic++; # If we can show detailed info, do so. if ( defined &Carp::longmess ) { # Don't recursively enter the warn handler, since we're carping. local $SIG{__WARN__} = ''; # Skip two levels before reporting traceback: we're skipping # mydie and confess. local $Carp::CarpLevel = 2; # mydie + confess # Tell us all about it. _db_warn( Carp::longmess("Signal @_") ); } # No Carp. Tell us about the signal as best we can. else { local $\ = ''; print $DB::OUT "Got signal @_\n"; } # Drop dead. kill 'ABRT', $$; } ## end sub diesignal sub dbwarn { # No entry/exit trace. local $frame = 0; # No return value printing. local $doret = -2; # Turn off warn and die handling to prevent recursive entries to this # routine. local $SIG{__WARN__} = ''; local $SIG{__DIE__} = ''; # Load Carp if we can. If $^S is false (current thing being compiled isn't # done yet), we may not be able to do a require. eval { require Carp } if defined $^S; # If error/warning during compilation, # require may be broken. # Use the core warn() unless Carp loaded OK. CORE::warn( @_, "\nCannot print stack trace, load with -MCarp option to see stack" ), return unless defined &Carp::longmess; # Save the current values of $single and $trace, and then turn them off. my ( $mysingle, $mytrace ) = ( $single, $trace ); $single = 0; $trace = 0; # We can call Carp::longmess without its being "debugged" (which we # don't want - we just want to use it!). Capture this for later. my $mess = Carp::longmess(@_); # Restore $single and $trace to their original values. ( $single, $trace ) = ( $mysingle, $mytrace ); # Use the debugger's own special way of printing warnings to print # the stack trace message. _db_warn($mess); } ## end sub dbwarn sub dbdie { local $frame = 0; local $doret = -2; local $SIG{__DIE__} = ''; local $SIG{__WARN__} = ''; if ( $dieLevel > 2 ) { local $SIG{__WARN__} = \&dbwarn; _db_warn(@_); # Yell no matter what return; } if ( $dieLevel < 2 ) { die @_ if $^S; # in eval propagate } # The code used to check $^S to see if compilation of the current thing # hadn't finished. We don't do it anymore, figuring eval is pretty stable. eval { require Carp }; die( @_, "\nCannot print stack trace, load with -MCarp option to see stack" ) unless defined &Carp::longmess; # We do not want to debug this chunk (automatic disabling works # inside DB::DB, but not in Carp). Save $single and $trace, turn them off, # get the stack trace from Carp::longmess (if possible), restore $signal # and $trace, and then die with the stack trace. my ( $mysingle, $mytrace ) = ( $single, $trace ); $single = 0; $trace = 0; my $mess = "@_"; { package Carp; # Do not include us in the list eval { $mess = Carp::longmess(@_); }; } ( $single, $trace ) = ( $mysingle, $mytrace ); die $mess; } ## end sub dbdie sub warnLevel { if (@_) { my $prevwarn = $SIG{__WARN__} unless $warnLevel; $warnLevel = shift; if ($warnLevel) { $SIG{__WARN__} = \&DB::dbwarn; } elsif ($prevwarn) { $SIG{__WARN__} = $prevwarn; } else { undef $SIG{__WARN__}; } } ## end if (@_) $warnLevel; } ## end sub warnLevel sub dieLevel { local $\ = ''; if (@_) { my $prevdie = $SIG{__DIE__} unless $dieLevel; $dieLevel = shift; if ($dieLevel) { # Always set it to dbdie() for non-zero values. $SIG{__DIE__} = \&DB::dbdie; # if $dieLevel < 2; # No longer exists, so don't try to use it. #$SIG{__DIE__} = \&DB::diehard if $dieLevel >= 2; # If we've finished initialization, mention that stack dumps # are enabled, If dieLevel is 1, we won't stack dump if we die # in an eval(). print $OUT "Stack dump during die enabled", ( $dieLevel == 1 ? " outside of evals" : "" ), ".\n" if $I_m_init; # XXX This is probably obsolete, given that diehard() is gone. print $OUT "Dump printed too.\n" if $dieLevel > 2; } ## end if ($dieLevel) # Put the old one back if there was one. elsif ($prevdie) { $SIG{__DIE__} = $prevdie; print $OUT "Default die handler restored.\n"; } else { undef $SIG{__DIE__}; print $OUT "Die handler removed.\n"; } } ## end if (@_) $dieLevel; } ## end sub dieLevel sub signalLevel { if (@_) { my $prevsegv = $SIG{SEGV} unless $signalLevel; my $prevbus = $SIG{BUS} unless $signalLevel; $signalLevel = shift; if ($signalLevel) { $SIG{SEGV} = \&DB::diesignal; $SIG{BUS} = \&DB::diesignal; } else { $SIG{SEGV} = $prevsegv; $SIG{BUS} = $prevbus; } } ## end if (@_) $signalLevel; } ## end sub signalLevel sub CvGV_name { my $in = shift; my $name = CvGV_name_or_bust($in); defined $name ? $name : $in; } use vars qw($skipCvGV); sub CvGV_name_or_bust { my $in = shift; return if $skipCvGV; # Backdoor to avoid problems if XS broken... return unless ref $in; $in = \&$in; # Hard reference... eval { require Devel::Peek; 1 } or return; my $gv = Devel::Peek::CvGV($in) or return; *$gv{PACKAGE} . '::' . *$gv{NAME}; } ## end sub CvGV_name_or_bust sub _find_sub_helper { my $subr = shift; return unless defined &$subr; my $name = CvGV_name_or_bust($subr); my $data; $data = $sub{$name} if defined $name; return $data if defined $data; # Old stupid way... $subr = \&$subr; # Hard reference my $s; for ( keys %sub ) { $s = $_, last if $subr eq \&$_; } if ($s) { return $sub{$s}; } else { return; } } sub find_sub { my $subr = shift; return ( $sub{$subr} || _find_sub_helper($subr) ); } ## end sub find_sub use vars qw(%seen); sub methods { # Figure out the class - either this is the class or it's a reference # to something blessed into that class. my $class = shift; $class = ref $class if ref $class; local %seen; # Show the methods that this class has. methods_via( $class, '', 1 ); # Show the methods that UNIVERSAL has. methods_via( 'UNIVERSAL', 'UNIVERSAL', 0 ); } ## end sub methods sub methods_via { # If we've processed this class already, just quit. my $class = shift; return if $seen{$class}++; # This is a package that is contributing the methods we're about to print. my $prefix = shift; my $prepend = $prefix ? "via $prefix: " : ''; my @to_print; # Extract from all the symbols in this class. my $class_ref = do { no strict "refs"; \%{$class . '::'} }; while (my ($name, $glob) = each %$class_ref) { # references directly in the symbol table are Proxy Constant # Subroutines, and are by their very nature defined # Otherwise, check if the thing is a typeglob, and if it is, it decays # to a subroutine reference, which can be tested by defined. # $glob might also be the value -1 (from sub foo;) # or (say) '$$' (from sub foo ($$);) # \$glob will be SCALAR in both cases. if ((ref $glob || ($glob && ref \$glob eq 'GLOB' && defined &$glob)) && !$seen{$name}++) { push @to_print, "$prepend$name\n"; } } { local $\ = ''; local $, = ''; print $DB::OUT $_ foreach sort @to_print; } # If the $crawl_upward argument is false, just quit here. return unless shift; # $crawl_upward true: keep going up the tree. # Find all the classes this one is a subclass of. my $class_ISA_ref = do { no strict "refs"; \@{"${class}::ISA"} }; for my $name ( @$class_ISA_ref ) { # Set up the new prefix. $prepend = $prefix ? $prefix . " -> $name" : $name; # Crawl up the tree and keep trying to crawl up. methods_via( $name, $prepend, 1 ); } } ## end sub methods_via sub setman { $doccmd = $^O !~ /^(?:MSWin32|VMS|os2|dos|amigaos|riscos|NetWare)\z/s ? "man" # O Happy Day! : "perldoc"; # Alas, poor unfortunates } ## end sub setman sub runman { my $page = shift; unless ($page) { _db_system("$doccmd $doccmd"); return; } # this way user can override, like with $doccmd="man -Mwhatever" # or even just "man " to disable the path check. if ( $doccmd ne 'man' ) { _db_system("$doccmd $page"); return; } $page = 'perl' if lc($page) eq 'help'; require Config; my $man1dir = $Config::Config{man1direxp}; my $man3dir = $Config::Config{man3direxp}; for ( $man1dir, $man3dir ) { s#/[^/]*\z## if /\S/ } my $manpath = ''; $manpath .= "$man1dir:" if $man1dir =~ /\S/; $manpath .= "$man3dir:" if $man3dir =~ /\S/ && $man1dir ne $man3dir; chop $manpath if $manpath; # harmless if missing, I figure local $ENV{MANPATH} = $manpath if $manpath; my $nopathopt = $^O =~ /dunno what goes here/; if ( CORE::system( $doccmd, # I just *know* there are men without -M ( ( $manpath && !$nopathopt ) ? ( "-M", $manpath ) : () ), split ' ', $page ) ) { unless ( $page =~ /^perl\w/ ) { # Previously the debugger contained a list which it slurped in, # listing the known "perl" manpages. However, it was out of date, # with errors both of omission and inclusion. This approach is # considerably less complex. The failure mode on a butchered # install is simply that the user has to run man or perldoc # "manually" with the full manpage name. # There is a list of $^O values in installperl to determine whether # the directory is 'pods' or 'pod'. However, we can avoid tight # coupling to that by simply checking the "non-standard" 'pods' # first. my $pods = "$Config::Config{privlibexp}/pods"; $pods = "$Config::Config{privlibexp}/pod" unless -d $pods; if (-f "$pods/perl$page.pod") { CORE::system( $doccmd, ( ( $manpath && !$nopathopt ) ? ( "-M", $manpath ) : () ), "perl$page" ); } } } ## end if (CORE::system($doccmd... } ## end sub runman #use Carp; # This did break, left for debugging # The following BEGIN is very handy if debugger goes havoc, debugging debugger? use vars qw($db_stop); BEGIN { # This does not compile, alas. (XXX eh?) $IN = \*STDIN; # For bugs before DB::OUT has been opened $OUT = \*STDERR; # For errors before DB::OUT has been opened # Define characters used by command parsing. $sh = '!'; # Shell escape (does not work) $rc = ','; # Recall command (does not work) @hist = ('?'); # Show history (does not work) @truehist = (); # Can be saved for replay (per session) # This defines the point at which you get the 'deep recursion' # warning. It MUST be defined or the debugger will not load. $deep = 1000; # Number of lines around the current one that are shown in the # 'w' command. $window = 10; # How much before-the-current-line context the 'v' command should # use in calculating the start of the window it will display. $preview = 3; # We're not in any sub yet, but we need this to be a defined value. $sub = ''; # Set up the debugger's interrupt handler. It simply sets a flag # ($signal) that DB::DB() will check before each command is executed. $SIG{INT} = \&DB::catch; # The following lines supposedly, if uncommented, allow the debugger to # debug itself. Perhaps we can try that someday. # This may be enabled to debug debugger: #$warnLevel = 1 unless defined $warnLevel; #$dieLevel = 1 unless defined $dieLevel; #$signalLevel = 1 unless defined $signalLevel; # This is the flag that says "a debugger is running, please call # DB::DB and DB::sub". We will turn it on forcibly before we try to # execute anything in the user's context, because we always want to # get control back. $db_stop = 0; # Compiler warning ... $db_stop = 1 << 30; # ... because this is only used in an eval() later. # This variable records how many levels we're nested in debugging. Used # Used in the debugger prompt, and in determining whether it's all over or # not. $level = 0; # Level of recursive debugging # "Triggers bug (?) in perl if we postpone this until runtime." # XXX No details on this yet, or whether we should fix the bug instead # of work around it. Stay tuned. @stack = (0); # Used to track the current stack depth using the auto-stacked-variable # trick. $stack_depth = 0; # Localized repeatedly; simple way to track $#stack # Don't print return values on exiting a subroutine. $doret = -2; # No extry/exit tracing. $frame = 0; } ## end BEGIN BEGIN { $^W = $ini_warn; } # Switch warnings back sub db_complete { # Specific code for b c l V m f O, &blah, $blah, @blah, %blah # $text is the text to be completed. # $line is the incoming line typed by the user. # $start is the start of the text to be completed in the incoming line. my ( $text, $line, $start ) = @_; # Save the initial text. # The search pattern is current package, ::, extract the next qualifier # Prefix and pack are set to undef. my ( $itext, $search, $prefix, $pack ) = ( $text, "^\Q${package}::\E([^:]+)\$" ); return sort grep /^\Q$text/, ( keys %sub ), qw(postpone load compile), # subroutines ( map { /$search/ ? ($1) : () } keys %sub ) if ( substr $line, 0, $start ) =~ /^\|*[blc]\s+((postpone|compile)\s+)?$/; return sort grep /^\Q$text/, values %INC # files if ( substr $line, 0, $start ) =~ /^\|*b\s+load\s+$/; return sort map { ( $_, db_complete( $_ . "::", "V ", 2 ) ) } grep /^\Q$text/, map { /^(.*)::$/ ? ($1) : () } keys %:: # top-packages if ( substr $line, 0, $start ) =~ /^\|*[Vm]\s+$/ and $text =~ /^\w*$/; return sort map { ( $_, db_complete( $_ . "::", "V ", 2 ) ) } grep !/^main::/, grep /^\Q$text/, map { /^(.*)::$/ ? ( $prefix . "::$1" ) : () } do { no strict 'refs'; keys %{ $prefix . '::' } } if ( substr $line, 0, $start ) =~ /^\|*[Vm]\s+$/ and $text =~ /^(.*[^:])::?(\w*)$/ and $prefix = $1; if ( $line =~ /^\|*f\s+(.*)/ ) { # Loaded files # We might possibly want to switch to an eval (which has a "filename" # like '(eval 9)'), so we may need to clean up the completion text # before proceeding. $prefix = length($1) - length($text); $text = $1; return sort map { substr $_, 2 + $prefix } grep /^_<\Q$text/, ( keys %main:: ), $0; } ## end if ($line =~ /^\|*f\s+(.*)/) if ( ( substr $text, 0, 1 ) eq '&' ) { # subroutines $text = substr $text, 1; $prefix = "&"; return sort map "$prefix$_", grep /^\Q$text/, ( keys %sub ), ( map { /$search/ ? ($1) : () } keys %sub ); } ## end if ((substr $text, 0, ... if ( $text =~ /^[\$@%](.*)::(.*)/ ) { # symbols in a package $pack = ( $1 eq 'main' ? '' : $1 ) . '::'; $prefix = ( substr $text, 0, 1 ) . $1 . '::'; $text = $2; my @out = do { no strict 'refs'; map "$prefix$_", grep /^\Q$text/, grep /^_?[a-zA-Z]/, keys %$pack; }; if ( @out == 1 and $out[0] =~ /::$/ and $out[0] ne $itext ) { return db_complete( $out[0], $line, $start ); } # Return the list of possibles. return sort @out; } ## end if ($text =~ /^[\$@%](.*)::(.*)/) if ( $text =~ /^[\$@%]/ ) { # symbols (in $package + packages in main) $pack = ( $package eq 'main' ? '' : $package ) . '::'; $prefix = substr $text, 0, 1; $text = substr $text, 1; my @out; if (not $text =~ /::/ and eval { local @INC = @INC; pop @INC if $INC[-1] eq '.'; require PadWalker } ) { my $level = 1; while (1) { my @info = caller($level); $level++; $level = -1, last if not @info; last if $info[3] eq 'DB::DB'; } if ($level > 0) { my $lexicals = PadWalker::peek_my($level); push @out, grep /^\Q$prefix$text/, keys %$lexicals; } } push @out, map "$prefix$_", grep /^\Q$text/, ( grep /^_?[a-zA-Z]/, do { no strict 'refs'; keys %$pack } ), ( $pack eq '::' ? () : ( grep /::$/, keys %:: ) ); if ( @out == 1 and $out[0] =~ /::$/ and $out[0] ne $itext ) { return db_complete( $out[0], $line, $start ); } # Return the list of possibles. return sort @out; } ## end if ($text =~ /^[\$@%]/) if ( ( substr $line, 0, $start ) =~ /^\|*[oO]\b.*\s$/ ) { # Options after space # We look for the text to be matched in the list of possible options, # and fetch the current value. my @out = grep /^\Q$text/, @options; my $val = option_val( $out[0], undef ); # Set up a 'query option's value' command. my $out = '? '; if ( not defined $val or $val =~ /[\n\r]/ ) { # There's really nothing else we can do. } # We have a value. Create a proper option-setting command. elsif ( $val =~ /\s/ ) { # XXX This may be an extraneous variable. my $found; # We'll want to quote the string (because of the embedded # whtespace), but we want to make sure we don't end up with # mismatched quote characters. We try several possibilities. foreach my $l ( split //, qq/\"\'\#\|/ ) { # If we didn't find this quote character in the value, # quote it using this quote character. $out = "$l$val$l ", last if ( index $val, $l ) == -1; } } ## end elsif ($val =~ /\s/) # Don't need any quotes. else { $out = "=$val "; } # If there were multiple possible values, return '? ', which # makes the command into a query command. If there was just one, # have readline append that. $rl_attribs->{completer_terminator_character} = ( @out == 1 ? $out : '? ' ); # Return list of possibilities. return sort @out; } ## end if ((substr $line, 0, ... return $term->filename_list($text); # filenames } ## end sub db_complete sub end_report { local $\ = ''; print $OUT "Use 'q' to quit or 'R' to restart. 'h q' for details.\n"; } sub clean_ENV { if ( defined($ini_pids) ) { $ENV{PERLDB_PIDS} = $ini_pids; } else { delete( $ENV{PERLDB_PIDS} ); } } ## end sub clean_ENV # PERLDBf_... flag names from perl.h our ( %DollarCaretP_flags, %DollarCaretP_flags_r ); BEGIN { %DollarCaretP_flags = ( PERLDBf_SUB => 0x01, # Debug sub enter/exit PERLDBf_LINE => 0x02, # Keep line # PERLDBf_NOOPT => 0x04, # Switch off optimizations PERLDBf_INTER => 0x08, # Preserve more data PERLDBf_SUBLINE => 0x10, # Keep subr source lines PERLDBf_SINGLE => 0x20, # Start with single-step on PERLDBf_NONAME => 0x40, # For _SUB: no name of the subr PERLDBf_GOTO => 0x80, # Report goto: call DB::goto PERLDBf_NAMEEVAL => 0x100, # Informative names for evals PERLDBf_NAMEANON => 0x200, # Informative names for anon subs PERLDBf_SAVESRC => 0x400, # Save source lines into @{"_<$filename"} PERLDB_ALL => 0x33f, # No _NONAME, _GOTO ); # PERLDBf_LINE also enables the actions of PERLDBf_SAVESRC, so the debugger # doesn't need to set it. It's provided for the benefit of profilers and # other code analysers. %DollarCaretP_flags_r = reverse %DollarCaretP_flags; } sub parse_DollarCaretP_flags { my $flags = shift; $flags =~ s/^\s+//; $flags =~ s/\s+$//; my $acu = 0; foreach my $f ( split /\s*\|\s*/, $flags ) { my $value; if ( $f =~ /^0x([[:xdigit:]]+)$/ ) { $value = hex $1; } elsif ( $f =~ /^(\d+)$/ ) { $value = int $1; } elsif ( $f =~ /^DEFAULT$/i ) { $value = $DollarCaretP_flags{PERLDB_ALL}; } else { $f =~ /^(?:PERLDBf_)?(.*)$/i; $value = $DollarCaretP_flags{ 'PERLDBf_' . uc($1) }; unless ( defined $value ) { print $OUT ( "Unrecognized \$^P flag '$f'!\n", "Acceptable flags are: " . join( ', ', sort keys %DollarCaretP_flags ), ", and hexadecimal and decimal numbers.\n" ); return undef; } } $acu |= $value; } $acu; } sub expand_DollarCaretP_flags { my $DollarCaretP = shift; my @bits = ( map { my $n = ( 1 << $_ ); ( $DollarCaretP & $n ) ? ( $DollarCaretP_flags_r{$n} || sprintf( '0x%x', $n ) ) : () } 0 .. 31 ); return @bits ? join( '|', @bits ) : 0; } sub rerun { my $i = shift; my @args; pop(@truehist); # strim unless (defined $truehist[$i]) { print "Unable to return to non-existent command: $i\n"; } else { $#truehist = ($i < 0 ? $#truehist + $i : $i > 0 ? $i : $#truehist); my @temp = @truehist; # store push(@DB::typeahead, @truehist); # saved @truehist = @hist = (); # flush @args = restart(); # setup get_list("PERLDB_HIST"); # clean set_list("PERLDB_HIST", @temp); # reset } return @args; } sub restart { # I may not be able to resurrect you, but here goes ... print $OUT "Warning: some settings and command-line options may be lost!\n"; my ( @script, @flags, $cl ); # If warn was on before, turn it on again. push @flags, '-w' if $ini_warn; # Rebuild the -I flags that were on the initial # command line. for (@ini_INC) { push @flags, '-I', $_; } # Turn on taint if it was on before. push @flags, '-T' if ${^TAINT}; # Arrange for setting the old INC: # Save the current @init_INC in the environment. set_list( "PERLDB_INC", @ini_INC ); # If this was a perl one-liner, go to the "file" # corresponding to the one-liner read all the lines # out of it (except for the first one, which is going # to be added back on again when 'perl -d' runs: that's # the 'require perl5db.pl;' line), and add them back on # to the command line to be executed. if ( $0 eq '-e' ) { my $lines = *{$main::{'_<-e'}}{ARRAY}; for ( 1 .. $#$lines ) { # The first line is PERL5DB chomp( $cl = $lines->[$_] ); push @script, '-e', $cl; } } ## end if ($0 eq '-e') # Otherwise we just reuse the original name we had # before. else { @script = $0; } # If the terminal supported history, grab it and # save that in the environment. set_list( "PERLDB_HIST", $term->Features->{getHistory} ? $term->GetHistory : @hist ); # Find all the files that were visited during this # session (i.e., the debugger had magic hashes # corresponding to them) and stick them in the environment. my @had_breakpoints = keys %had_breakpoints; set_list( "PERLDB_VISITED", @had_breakpoints ); # Save the debugger options we chose. set_list( "PERLDB_OPT", %option ); # set_list( "PERLDB_OPT", options2remember() ); # Save the break-on-loads. set_list( "PERLDB_ON_LOAD", %break_on_load ); # Go through all the breakpoints and make sure they're # still valid. my @hard; for ( 0 .. $#had_breakpoints ) { # We were in this file. my $file = $had_breakpoints[$_]; # Grab that file's magic line hash. *dbline = $main::{ '_<' . $file }; # Skip out if it doesn't exist, or if the breakpoint # is in a postponed file (we'll do postponed ones # later). next unless %dbline or $postponed_file{$file}; # In an eval. This is a little harder, so we'll # do more processing on that below. ( push @hard, $file ), next if $file =~ /^\(\w*eval/; # XXX I have no idea what this is doing. Yet. my @add; @add = %{ $postponed_file{$file} } if $postponed_file{$file}; # Save the list of all the breakpoints for this file. set_list( "PERLDB_FILE_$_", %dbline, @add ); # Serialize the extra data %breakpoints_data hash. # That's a bug fix. set_list( "PERLDB_FILE_ENABLED_$_", map { _is_breakpoint_enabled($file, $_) ? 1 : 0 } sort { $a <=> $b } keys(%dbline) ) } ## end for (0 .. $#had_breakpoints) # The breakpoint was inside an eval. This is a little # more difficult. XXX and I don't understand it. foreach my $hard_file (@hard) { # Get over to the eval in question. *dbline = $main::{ '_<' . $hard_file }; my $quoted = quotemeta $hard_file; my %subs; for my $sub ( keys %sub ) { if (my ($n1, $n2) = $sub{$sub} =~ /\A$quoted:(\d+)-(\d+)\z/) { $subs{$sub} = [ $n1, $n2 ]; } } unless (%subs) { print {$OUT} "No subroutines in $hard_file, ignoring breakpoints.\n"; next; } LINES: foreach my $line ( keys %dbline ) { # One breakpoint per sub only: my ( $offset, $found ); SUBS: foreach my $sub ( keys %subs ) { if ( $subs{$sub}->[1] >= $line # Not after the subroutine and ( not defined $offset # Not caught or $offset < 0 ) ) { # or badly caught $found = $sub; $offset = $line - $subs{$sub}->[0]; if ($offset >= 0) { $offset = "+$offset"; last SUBS; } } ## end if ($subs{$sub}->[1] >=... } ## end for $sub (keys %subs) if ( defined $offset ) { $postponed{$found} = "break $offset if $dbline{$line}"; } else { print {$OUT} ("Breakpoint in ${hard_file}:$line ignored:" . " after all the subroutines.\n"); } } ## end for $line (keys %dbline) } ## end for (@hard) # Save the other things that don't need to be # processed. set_list( "PERLDB_POSTPONE", %postponed ); set_list( "PERLDB_PRETYPE", @$pretype ); set_list( "PERLDB_PRE", @$pre ); set_list( "PERLDB_POST", @$post ); set_list( "PERLDB_TYPEAHEAD", @typeahead ); # We are officially restarting. $ENV{PERLDB_RESTART} = 1; # We are junking all child debuggers. delete $ENV{PERLDB_PIDS}; # Restore ini state # Set this back to the initial pid. $ENV{PERLDB_PIDS} = $ini_pids if defined $ini_pids; # And run Perl again. Add the "-d" flag, all the # flags we built up, the script (whether a one-liner # or a file), add on the -emacs flag for a slave editor, # and then the old arguments. return ($^X, '-d', @flags, @script, ($slave_editor ? '-emacs' : ()), @ARGS); }; # end restart END { $finished = 1 if $inhibit_exit; # So that some commands may be disabled. $fall_off_end = 1 unless $inhibit_exit; # Do not stop in at_exit() and destructors on exit: if ($fall_off_end or $runnonstop) { save_hist(); } else { $DB::single = 1; DB::fake::at_exit(); } } ## end END sub cmd_pre580_null { # do nothing... } sub cmd_pre580_a { my $xcmd = shift; my $cmd = shift; # Argument supplied. Add the action. if ( $cmd =~ /^(\d*)\s*(.*)/ ) { # If the line isn't there, use the current line. my $i = $1 || $line; my $j = $2; # If there is an action ... if ( length $j ) { # ... but the line isn't breakable, skip it. if ( $dbline[$i] == 0 ) { print $OUT "Line $i may not have an action.\n"; } else { # ... and the line is breakable: # Mark that there's an action in this file. $had_breakpoints{$filename} |= 2; # Delete any current action. $dbline{$i} =~ s/\0[^\0]*//; # Add the new action, continuing the line as needed. $dbline{$i} .= "\0" . action($j); } } ## end if (length $j) # No action supplied. else { # Delete the action. $dbline{$i} =~ s/\0[^\0]*//; # Mark as having no break or action if nothing's left. delete $dbline{$i} if $dbline{$i} eq ''; } } ## end if ($cmd =~ /^(\d*)\s*(.*)/) } ## end sub cmd_pre580_a sub cmd_pre580_b { my $xcmd = shift; my $cmd = shift; my $dbline = shift; # Break on load. if ( $cmd =~ /^load\b\s*(.*)/ ) { my $file = $1; $file =~ s/\s+$//; cmd_b_load($file); } # b compile|postpone [] # The interpreter actually traps this one for us; we just put the # necessary condition in the %postponed hash. elsif ( $cmd =~ /^(postpone|compile)\b\s*([':A-Za-z_][':\w]*)\s*(.*)/ ) { # Capture the condition if there is one. Make it true if none. my $cond = length $3 ? $3 : '1'; # Save the sub name and set $break to 1 if $1 was 'postpone', 0 # if it was 'compile'. my ( $subname, $break ) = ( $2, $1 eq 'postpone' ); # De-Perl4-ify the name - ' separators to ::. $subname =~ s/\'/::/g; # Qualify it into the current package unless it's already qualified. $subname = "${package}::" . $subname unless $subname =~ /::/; # Add main if it starts with ::. $subname = "main" . $subname if substr( $subname, 0, 2 ) eq "::"; # Save the break type for this sub. $postponed{$subname} = $break ? "break +0 if $cond" : "compile"; } ## end elsif ($cmd =~ ... # b [] elsif ( $cmd =~ /^([':A-Za-z_][':\w]*(?:\[.*\])?)\s*(.*)/ ) { my $subname = $1; my $cond = length $2 ? $2 : '1'; cmd_b_sub( $subname, $cond ); } # b []. elsif ( $cmd =~ /^(\d*)\s*(.*)/ ) { my $i = $1 || $dbline; my $cond = length $2 ? $2 : '1'; cmd_b_line( $i, $cond ); } } ## end sub cmd_pre580_b sub cmd_pre580_D { my $xcmd = shift; my $cmd = shift; if ( $cmd =~ /^\s*$/ ) { print $OUT "Deleting all breakpoints...\n"; # %had_breakpoints lists every file that had at least one # breakpoint in it. my $file; for $file ( keys %had_breakpoints ) { # Switch to the desired file temporarily. local *dbline = $main::{ '_<' . $file }; $max = $#dbline; my $was; # For all lines in this file ... for my $i (1 .. $max) { # If there's a breakpoint or action on this line ... if ( defined $dbline{$i} ) { # ... remove the breakpoint. $dbline{$i} =~ s/^[^\0]+//; if ( $dbline{$i} =~ s/^\0?$// ) { # Remove the entry altogether if no action is there. delete $dbline{$i}; } } ## end if (defined $dbline{$i... } ## end for my $i (1 .. $max) # If, after we turn off the "there were breakpoints in this file" # bit, the entry in %had_breakpoints for this file is zero, # we should remove this file from the hash. if ( not $had_breakpoints{$file} &= ~1 ) { delete $had_breakpoints{$file}; } } ## end for $file (keys %had_breakpoints) # Kill off all the other breakpoints that are waiting for files that # haven't been loaded yet. undef %postponed; undef %postponed_file; undef %break_on_load; } ## end if ($cmd =~ /^\s*$/) } ## end sub cmd_pre580_D sub cmd_pre580_h { my $xcmd = shift; my $cmd = shift; # Print the *right* help, long format. if ( $cmd =~ /^\s*$/ ) { print_help($pre580_help); } # 'h h' - explicitly-requested summary. elsif ( $cmd =~ /^h\s*/ ) { print_help($pre580_summary); } # Find and print a command's help. elsif ( $cmd =~ /^h\s+(\S.*)$/ ) { my $asked = $1; # for proper errmsg my $qasked = quotemeta($asked); # for searching # XXX: finds CR but not if ( $pre580_help =~ /^ is not a debugger command.\n"); } } ## end elsif ($cmd =~ /^h\s+(\S.*)$/) } ## end sub cmd_pre580_h sub cmd_pre580_W { my $xcmd = shift; my $cmd = shift; # Delete all watch expressions. if ( $cmd =~ /^$/ ) { # No watching is going on. $trace &= ~2; # Kill all the watch expressions and values. @to_watch = @old_watch = (); } # Add a watch expression. elsif ( $cmd =~ /^(.*)/s ) { # add it to the list to be watched. push @to_watch, $1; # Get the current value of the expression. # Doesn't handle expressions returning list values! $evalarg = $1; # The &-call is here to ascertain the mutability of @_. my ($val) = &DB::eval; $val = ( defined $val ) ? "'$val'" : 'undef'; # Save it. push @old_watch, $val; # We're watching stuff. $trace |= 2; } ## end elsif ($cmd =~ /^(.*)/s) } ## end sub cmd_pre580_W sub cmd_pre590_prepost { my $cmd = shift; my $line = shift || '*'; my $dbline = shift; return cmd_prepost( $cmd, $line, $dbline ); } ## end sub cmd_pre590_prepost sub cmd_prepost { my $cmd = shift; # No action supplied defaults to 'list'. my $line = shift || '?'; # Figure out what to put in the prompt. my $which = ''; # Make sure we have some array or another to address later. # This means that if for some reason the tests fail, we won't be # trying to stash actions or delete them from the wrong place. my $aref = []; # < - Perl code to run before prompt. if ( $cmd =~ /^\ - Perl code to run after prompt. elsif ( $cmd =~ /^\>/o ) { $which = 'post-perl'; $aref = $post; } # { - first check for properly-balanced braces. elsif ( $cmd =~ /^\{/o ) { if ( $cmd =~ /^\{.*\}$/o && unbalanced( substr( $cmd, 1 ) ) ) { print $OUT "$cmd is now a debugger command\nuse ';$cmd' if you mean Perl code\n"; } # Properly balanced. Pre-prompt debugger actions. else { $which = 'pre-debugger'; $aref = $pretype; } } ## end elsif ( $cmd =~ /^\{/o ) # Did we find something that makes sense? unless ($which) { print $OUT "Confused by command: $cmd\n"; } # Yes. else { # List actions. if ( $line =~ /^\s*\?\s*$/o ) { unless (@$aref) { # Nothing there. Complain. print $OUT "No $which actions.\n"; } else { # List the actions in the selected list. print $OUT "$which commands:\n"; foreach my $action (@$aref) { print $OUT "\t$cmd -- $action\n"; } } ## end else } ## end if ( $line =~ /^\s*\?\s*$/o) # Might be a delete. else { if ( length($cmd) == 1 ) { if ( $line =~ /^\s*\*\s*$/o ) { # It's a delete. Get rid of the old actions in the # selected list.. @$aref = (); print $OUT "All $cmd actions cleared.\n"; } else { # Replace all the actions. (This is a <, >, or {). @$aref = action($line); } } ## end if ( length($cmd) == 1) elsif ( length($cmd) == 2 ) { # Add the action to the line. (This is a <<, >>, or {{). push @$aref, action($line); } else { # <<<, >>>>, {{{{{{ ... something not a command. print $OUT "Confused by strange length of $which command($cmd)...\n"; } } ## end else [ if ( $line =~ /^\s*\?\s*$/o) } ## end else } ## end sub cmd_prepost package DB::fake; sub at_exit { "Debugged program terminated. Use 'q' to quit or 'R' to restart."; } package DB; # Do not trace this 1; below! 1; package if; $VERSION = '0.0608'; sub work { my $method = shift() ? 'import' : 'unimport'; unless (@_ >= 2) { my $type = ($method eq 'import') ? 'use' : 'no'; die "Too few arguments to '$type if' (some code returning an empty list in list context?)" } return unless shift; # CONDITION my $p = $_[0]; # PACKAGE (my $file = "$p.pm") =~ s!::!/!g; require $file; # Works even if $_[0] is a keyword (like open) my $m = $p->can($method); goto &$m if $m; } sub import { shift; unshift @_, 1; goto &work } sub unimport { shift; unshift @_, 0; goto &work } 1; __END__ package FileCache; our $VERSION = '1.10'; require 5.006; use Carp; use strict; no strict 'refs'; # These are not C for legacy reasons. # Previous versions requested the user set $cacheout_maxopen by hand. # Some authors fiddled with %saw to overcome the clobber on initial open. our %saw; our $cacheout_maxopen = 16; use parent 'Exporter'; our @EXPORT = qw[cacheout cacheout_close]; my %isopen; my $cacheout_seq = 0; sub import { my ($pkg,%args) = @_; # Use Exporter. %args are for us, not Exporter. # Make sure to up export_to_level, or we will import into ourselves, # rather than our calling package; __PACKAGE__->export_to_level(1); Exporter::import( $pkg ); # Truth is okay here because setting maxopen to 0 would be bad return $cacheout_maxopen = $args{maxopen} if $args{maxopen}; # XXX This code is crazy. Why is it a one element foreach loop? # Why is it using $param both as a filename and filehandle? foreach my $param ( '/usr/include/sys/param.h' ){ if (open($param, '<', $param)) { local ($_, $.); while (<$param>) { if( /^\s*#\s*define\s+NOFILE\s+(\d+)/ ){ $cacheout_maxopen = $1 - 4; close($param); last; } } close $param; } } $cacheout_maxopen ||= 16; } # Open in their package. sub cacheout_open { return open(*{caller(1) . '::' . $_[1]}, $_[0], $_[1]) && $_[1]; } # Close in their package. sub cacheout_close { # Short-circuit in case the filehandle disappeared my $pkg = caller($_[1]||0); defined fileno(*{$pkg . '::' . $_[0]}) && CORE::close(*{$pkg . '::' . $_[0]}); delete $isopen{$_[0]}; } # But only this sub name is visible to them. sub cacheout { my($mode, $file, $class, $ret, $ref, $narg); croak "Not enough arguments for cacheout" unless $narg = scalar @_; croak "Too many arguments for cacheout" if $narg > 2; ($mode, $file) = @_; ($file, $mode) = ($mode, $file) if $narg == 1; croak "Invalid mode for cacheout" if $mode && ( $mode !~ /^\s*(?:>>|\+?>|\+?<|\|\-|)|\-\|\s*$/ ); # Mode changed? if( $isopen{$file} && ($mode||'>') ne $isopen{$file}->[1] ){ &cacheout_close($file, 1); } if( $isopen{$file}) { $ret = $file; $isopen{$file}->[0]++; } else{ if( scalar keys(%isopen) > $cacheout_maxopen -1 ) { my @lru = sort{ $isopen{$a}->[0] <=> $isopen{$b}->[0] } keys(%isopen); $cacheout_seq = 0; $isopen{$_}->[0] = $cacheout_seq++ for splice(@lru, int($cacheout_maxopen / 3)||$cacheout_maxopen); &cacheout_close($_, 1) for @lru; } unless( $ref ){ $mode ||= $saw{$file} ? '>>' : ($saw{$file}=1, '>'); } #XXX should we just return the value from cacheout_open, no croak? $ret = cacheout_open($mode, $file) or croak("Can't create $file: $!"); $isopen{$file} = [++$cacheout_seq, $mode]; } return $ret; } 1; package Digest; use strict; use vars qw($VERSION %MMAP $AUTOLOAD); $VERSION = "1.17_01"; %MMAP = ( "SHA-1" => [["Digest::SHA", 1], "Digest::SHA1", ["Digest::SHA2", 1]], "SHA-224" => [["Digest::SHA", 224]], "SHA-256" => [["Digest::SHA", 256], ["Digest::SHA2", 256]], "SHA-384" => [["Digest::SHA", 384], ["Digest::SHA2", 384]], "SHA-512" => [["Digest::SHA", 512], ["Digest::SHA2", 512]], "HMAC-MD5" => "Digest::HMAC_MD5", "HMAC-SHA-1" => "Digest::HMAC_SHA1", "CRC-16" => [["Digest::CRC", type => "crc16"]], "CRC-32" => [["Digest::CRC", type => "crc32"]], "CRC-CCITT" => [["Digest::CRC", type => "crcccitt"]], "RIPEMD-160" => "Crypt::RIPEMD160", ); sub new { shift; # class ignored my $algorithm = shift; my $impl = $MMAP{$algorithm} || do { $algorithm =~ s/\W+//g; "Digest::$algorithm"; }; $impl = [$impl] unless ref($impl); local $@; # don't clobber it for our caller my $err; for (@$impl) { my $class = $_; my @args; ($class, @args) = @$class if ref($class); no strict 'refs'; unless (exists ${"$class\::"}{"VERSION"}) { my $pm_file = $class . ".pm"; $pm_file =~ s{::}{/}g; eval { local @INC = @INC; pop @INC if $INC[-1] eq '.'; require $pm_file }; if ($@) { $err ||= $@; next; } } return $class->new(@args, @_); } die $err; } sub AUTOLOAD { my $class = shift; my $algorithm = substr($AUTOLOAD, rindex($AUTOLOAD, '::')+2); $class->new($algorithm, @_); } 1; __END__ package utf8; $utf8::hint_bits = 0x00800000; our $VERSION = '1.21'; sub import { $^H |= $utf8::hint_bits; } sub unimport { $^H &= ~$utf8::hint_bits; } sub AUTOLOAD { require "utf8_heavy.pl"; goto &$AUTOLOAD if defined &$AUTOLOAD; require Carp; Carp::croak("Undefined subroutine $AUTOLOAD called"); } 1; __END__ package overloading; use warnings; our $VERSION = '0.02'; my $HINT_NO_AMAGIC = 0x01000000; # see perl.h require 5.010001; sub _ops_to_nums { require overload::numbers; map { exists $overload::numbers::names{"($_"} ? $overload::numbers::names{"($_"} : do { require Carp; Carp::croak("'$_' is not a valid overload") } } @_; } sub import { my ( $class, @ops ) = @_; if ( @ops ) { if ( $^H{overloading} ) { vec($^H{overloading} , $_, 1) = 0 for _ops_to_nums(@ops); } if ( $^H{overloading} !~ /[^\0]/ ) { delete $^H{overloading}; $^H &= ~$HINT_NO_AMAGIC; } } else { delete $^H{overloading}; $^H &= ~$HINT_NO_AMAGIC; } } sub unimport { my ( $class, @ops ) = @_; if ( exists $^H{overloading} or not $^H & $HINT_NO_AMAGIC ) { if ( @ops ) { vec($^H{overloading} ||= '', $_, 1) = 1 for _ops_to_nums(@ops); } else { delete $^H{overloading}; } } $^H |= $HINT_NO_AMAGIC; } 1; __END__ package SelectSaver; our $VERSION = '1.02'; require 5.000; use Carp; use Symbol; sub new { @_ >= 1 && @_ <= 2 or croak 'usage: SelectSaver->new( [FILEHANDLE] )'; my $fh = select; my $self = bless \$fh, $_[0]; select qualify($_[1], caller) if @_ > 1; $self; } sub DESTROY { my $self = $_[0]; select $$self; } 1; package bytes; our $VERSION = '1.06'; $bytes::hint_bits = 0x00000008; sub import { $^H |= $bytes::hint_bits; } sub unimport { $^H &= ~$bytes::hint_bits; } sub AUTOLOAD { require "bytes_heavy.pl"; goto &$AUTOLOAD if defined &$AUTOLOAD; require Carp; Carp::croak("Undefined subroutine $AUTOLOAD called"); } sub length (_); sub chr (_); sub ord (_); sub substr ($$;$$); sub index ($$;$); sub rindex ($$;$); 1; __END__ package experimental; $experimental::VERSION = '0.019'; use strict; use warnings; use version (); BEGIN { eval { require feature } }; use Carp qw/croak carp/; my %warnings = map { $_ => 1 } grep { /^experimental::/ } keys %warnings::Offsets; my %features = map { $_ => 1 } $] > 5.015006 ? keys %feature::feature : do { my @features; if ($] >= 5.010) { push @features, qw/switch say state/; push @features, 'unicode_strings' if $] > 5.011002; } @features; }; my %min_version = ( array_base => '5', autoderef => '5.14.0', bitwise => '5.22.0', const_attr => '5.22.0', current_sub => '5.16.0', evalbytes => '5.16.0', fc => '5.16.0', lexical_topic => '5.10.0', lexical_subs => '5.18.0', postderef => '5.20.0', postderef_qq => '5.20.0', refaliasing => '5.22.0', regex_sets => '5.18.0', say => '5.10.0', smartmatch => '5.10.0', signatures => '5.20.0', state => '5.10.0', switch => '5.10.0', unicode_eval => '5.16.0', unicode_strings => '5.12.0', ); my %max_version = ( autoderef => '5.23.1', lexical_topic => '5.23.4', ); $_ = version->new($_) for values %min_version; $_ = version->new($_) for values %max_version; my %additional = ( postderef => ['postderef_qq'], switch => ['smartmatch'], ); sub _enable { my $pragma = shift; if ($warnings{"experimental::$pragma"}) { warnings->unimport("experimental::$pragma"); feature->import($pragma) if exists $features{$pragma}; _enable(@{ $additional{$pragma} }) if $additional{$pragma}; } elsif ($features{$pragma}) { feature->import($pragma); _enable(@{ $additional{$pragma} }) if $additional{$pragma}; } elsif (not exists $min_version{$pragma}) { croak "Can't enable unknown feature $pragma"; } elsif ($] < $min_version{$pragma}) { my $stable = $min_version{$pragma}; if ($stable->{version}[1] % 2) { $stable = version->new( "5.".($stable->{version}[1]+1).'.0' ); } croak "Need perl $stable or later for feature $pragma"; } elsif ($] >= ($max_version{$pragma} || 7)) { croak "Experimental feature $pragma has been removed from perl in version $max_version{$pragma}"; } } sub import { my ($self, @pragmas) = @_; for my $pragma (@pragmas) { _enable($pragma); } return; } sub _disable { my $pragma = shift; if ($warnings{"experimental::$pragma"}) { warnings->import("experimental::$pragma"); feature->unimport($pragma) if exists $features{$pragma}; _disable(@{ $additional{$pragma} }) if $additional{$pragma}; } elsif ($features{$pragma}) { feature->unimport($pragma); _disable(@{ $additional{$pragma} }) if $additional{$pragma}; } elsif (not exists $min_version{$pragma}) { carp "Can't disable unknown feature $pragma, ignoring"; } } sub unimport { my ($self, @pragmas) = @_; for my $pragma (@pragmas) { _disable($pragma); } return; } 1; #ABSTRACT: Experimental features made easy __END__ package Test2; use strict; use warnings; our $VERSION = '1.302133'; 1; __END__ package utf8; use strict; use warnings; use re "/aa"; # So we won't even try to look at above Latin1, potentially # resulting in a recursive call sub DEBUG () { 0 } $|=1 if DEBUG; sub DESTROY {} my %Cache; sub croak { require Carp; Carp::croak(@_) } sub _loose_name ($) { # Given a lowercase property or property-value name, return its # standardized version that is expected for look-up in the 'loose' hashes # in Heavy.pl (hence, this depends on what mktables does). This squeezes # out blanks, underscores and dashes. The complication stems from the # grandfathered-in 'L_', which retains a single trailing underscore. (my $loose = $_[0]) =~ s/[-_ \t]//g; return $loose if $loose !~ / ^ (?: is | to )? l $/x; return 'l_' if $_[0] =~ / l .* _ /x; # If original had a trailing '_' return $loose; } ## ## "SWASH" == "SWATCH HASH". A "swatch" is a swatch of the Unicode landscape. ## It's a data structure that encodes a set of Unicode characters. ## { # If a floating point number is within this distance from the value of a # fraction, it is considered to be that fraction, even if many more digits # are specified that don't exactly match. my $min_floating_slop; # To guard against this program calling something that in turn ends up # calling this program with the same inputs, and hence infinitely # recursing, we keep a stack of the properties that are currently in # progress, pushed upon entry, popped upon return. my @recursed; sub SWASHNEW { my ($class, $type, $list, $minbits, $none) = @_; my $user_defined = 0; local $^D = 0 if $^D; $class = "" unless defined $class; print STDERR __LINE__, ": class=$class, type=$type, list=", (defined $list) ? $list : ':undef:', ", minbits=$minbits, none=$none\n" if DEBUG; ## ## Get the list of codepoints for the type. ## Called from swash_init (see utf8.c) or SWASHNEW itself. ## ## Callers of swash_init: ## op.c:pmtrans -- for tr/// and y/// ## regexec.c:regclass_swash -- for /[]/, \p, and \P ## utf8.c:is_utf8_common -- for common Unicode properties ## utf8.c:S__to_utf8_case -- for lc, uc, ucfirst, etc. and //i ## Unicode::UCD::prop_invlist ## Unicode::UCD::prop_invmap ## ## Given a $type, our goal is to fill $list with the set of codepoint ## ranges. If $type is false, $list passed is used. ## ## $minbits: ## For binary properties, $minbits must be 1. ## For character mappings (case and transliteration), $minbits must ## be a number except 1. ## ## $list (or that filled according to $type): ## Refer to perlunicode.pod, "User-Defined Character Properties." ## ## For binary properties, only characters with the property value ## of True should be listed. The 3rd column, if any, will be ignored ## ## $none is undocumented, so I'm (khw) trying to do some documentation ## of it now. It appears to be if there is a mapping in an input file ## that maps to 'XXXX', then that is replaced by $none+1, expressed in ## hexadecimal. It is used somehow in tr///. ## ## To make the parsing of $type clear, this code takes the a rather ## unorthodox approach of last'ing out of the block once we have the ## info we need. Were this to be a subroutine, the 'last' would just ## be a 'return'. ## # If a problem is found $type is returned; # Upon success, a new (or cached) blessed object is returned with # keys TYPE, BITS, EXTRAS, LIST, and NONE with values having the # same meanings as the input parameters. # SPECIALS contains a reference to any special-treatment hash in the # property. # INVERT_IT is non-zero if the result should be inverted before use # USER_DEFINED is non-zero if the result came from a user-defined my $file; ## file to load data from, and also part of the %Cache key. # Change this to get a different set of Unicode tables my $unicore_dir = 'unicore'; my $invert_it = 0; my $list_is_from_mktables = 0; # Is $list returned from a mktables # generated file? If so, we know it's # well behaved. if ($type) { # Verify that this isn't a recursive call for this property. # Can't use croak, as it may try to recurse to here itself. my $class_type = $class . "::$type"; if (grep { $_ eq $class_type } @recursed) { CORE::die "panic: Infinite recursion in SWASHNEW for '$type'\n"; } push @recursed, $class_type; $type =~ s/^\s+//; $type =~ s/\s+$//; # regcomp.c surrounds the property name with '__" and '_i' if this # is to be caseless matching. my $caseless = $type =~ s/^(.*)__(.*)_i$/$1$2/; print STDERR __LINE__, ": type=$type, caseless=$caseless\n" if DEBUG; GETFILE: { ## ## It could be a user-defined property. Look in current ## package if no package given ## my $caller0 = caller(0); my $caller1 = $type =~ s/(.+)::// ? $1 : $caller0 eq 'main' ? 'main' : caller(1); if (defined $caller1 && $type =~ /^I[ns]\w+$/) { my $prop = "${caller1}::$type"; if (exists &{$prop}) { # stolen from Scalar::Util::PP::tainted() my $tainted; { local($@, $SIG{__DIE__}, $SIG{__WARN__}); local $^W = 0; no warnings; eval { kill 0 * $prop }; $tainted = 1 if $@ =~ /^Insecure/; } die "Insecure user-defined property \\p{$prop}\n" if $tainted; no strict 'refs'; $list = &{$prop}($caseless); $user_defined = 1; last GETFILE; } } # During Perl's compilation, this routine may be called before # the tables are constructed. If so, we have a chicken/egg # problem. If we die, the tables never get constructed, so # keep going, but return an empty table so only what the code # has compiled in internally (currently ASCII/Latin1 range # matching) will work. BEGIN { # Poor man's constant, to avoid a run-time check. $utf8::{miniperl} = \! defined &DynaLoader::boot_DynaLoader; } if (miniperl) { eval "require '$unicore_dir/Heavy.pl'"; if ($@) { print STDERR __LINE__, ": '$@'\n" if DEBUG; pop @recursed if @recursed; return $type; } } else { require "$unicore_dir/Heavy.pl"; } BEGIN { delete $utf8::{miniperl} } # All property names are matched caselessly my $property_and_table = CORE::lc $type; print STDERR __LINE__, ": $property_and_table\n" if DEBUG; # See if is of the compound form 'property=value', where the # value indicates the table we should use. my ($property, $table, @remainder) = split /\s*[:=]\s*/, $property_and_table, -1; if (@remainder) { pop @recursed if @recursed; return $type; } my $prefix; if (! defined $table) { # Here, is the single form. The property becomes empty, and # the whole value is the table. $table = $property; $prefix = $property = ""; } else { print STDERR __LINE__, ": $property\n" if DEBUG; # Here it is the compound property=table form. The property # name is always loosely matched, and always can have an # optional 'is' prefix (which isn't true in the single # form). $property = _loose_name($property) =~ s/^is//r; # And convert to canonical form. Quit if not valid. $property = $utf8::loose_property_name_of{$property}; if (! defined $property) { pop @recursed if @recursed; return $type; } $prefix = "$property="; # If the rhs looks like it is a number... print STDERR __LINE__, ": table=$table\n" if DEBUG; if ($table =~ m{ ^ [ \s 0-9 _ + / . -]+ $ }x) { print STDERR __LINE__, ": table=$table\n" if DEBUG; # Don't allow leading nor trailing slashes if ($table =~ / ^ \/ | \/ $ /x) { pop @recursed if @recursed; return $type; } # Split on slash, in case it is a rational, like \p{1/5} my @parts = split m{ \s* / \s* }x, $table, -1; print __LINE__, ": $type\n" if @parts > 2 && DEBUG; # Can have maximum of one slash if (@parts > 2) { pop @recursed if @recursed; return $type; } foreach my $part (@parts) { print __LINE__, ": part=$part\n" if DEBUG; $part =~ s/^\+\s*//; # Remove leading plus $part =~ s/^-\s*/-/; # Remove blanks after unary # minus # Remove underscores between digits. $part =~ s/(?<= [0-9] ) _ (?= [0-9] ) //xg; # No leading zeros (but don't make a single '0' # into a null string) $part =~ s/ ^ ( -? ) 0+ /$1/x; $part .= '0' if $part eq '-' || $part eq ""; # No trailing zeros after a decimal point $part =~ s/ ( \. .*? ) 0+ $ /$1/x; # Begin with a 0 if a leading decimal point $part =~ s/ ^ ( -? ) \. /${1}0./x; # Ensure not a trailing decimal point: turn into an # integer $part =~ s/ \. $ //x; print STDERR __LINE__, ": part=$part\n" if DEBUG; #return $type if $part eq ""; # Result better look like a number. (This test is # needed because, for example could have a plus in # the middle.) if ($part !~ / ^ -? [0-9]+ ( \. [0-9]+)? $ /x) { pop @recursed if @recursed; return $type; } } # If a rational... if (@parts == 2) { # If denominator is negative, get rid of it, and ... if ($parts[1] =~ s/^-//) { # If numerator is also negative, convert the # whole thing to positive, else move the minus # to the numerator if ($parts[0] !~ s/^-//) { $parts[0] = '-' . $parts[0]; } } $table = join '/', @parts; } elsif ($property ne 'nv' || $parts[0] !~ /\./) { # Here is not numeric value, or doesn't have a # decimal point. No further manipulation is # necessary. (Note the hard-coded property name. # This could fail if other properties eventually # had fractions as well; perhaps the cjk ones # could evolve to do that. This hard-coding could # be fixed by mktables generating a list of # properties that could have fractions.) $table = $parts[0]; } else { # Here is a floating point numeric_value. Try to # convert to rational. First see if is in the list # of known ones. if (exists $utf8::nv_floating_to_rational{$parts[0]}) { $table = $utf8::nv_floating_to_rational{$parts[0]}; } else { # Here not in the list. See if is close # enough to something in the list. First # determine what 'close enough' means. It has # to be as tight as what mktables says is the # maximum slop, and as tight as how many # digits we were passed. That is, if the user # said .667, .6667, .66667, etc. we match as # many digits as they passed until get to # where it doesn't matter any more due to the # machine's precision. If they said .6666668, # we fail. (my $fraction = $parts[0]) =~ s/^.*\.//; my $epsilon = 10 ** - (length($fraction)); if ($epsilon > $utf8::max_floating_slop) { $epsilon = $utf8::max_floating_slop; } # But it can't be tighter than the minimum # precision for this machine. If haven't # already calculated that minimum, do so now. if (! defined $min_floating_slop) { # Keep going down an order of magnitude # until find that adding this quantity to # 1 remains 1; but put an upper limit on # this so in case this algorithm doesn't # work properly on some platform, that we # won't loop forever. my $count = 0; $min_floating_slop = 1; while (1+ $min_floating_slop != 1 && $count++ < 50) { my $next = $min_floating_slop / 10; last if $next == 0; # If underflows, # use previous one $min_floating_slop = $next; print STDERR __LINE__, ": min_float_slop=$min_floating_slop\n" if DEBUG; } # Back off a couple orders of magnitude, # just to be safe. $min_floating_slop *= 100; } if ($epsilon < $min_floating_slop) { $epsilon = $min_floating_slop; } print STDERR __LINE__, ": fraction=.$fraction; epsilon=$epsilon\n" if DEBUG; undef $table; # And for each possible rational in the table, # see if it is within epsilon of the input. foreach my $official (keys %utf8::nv_floating_to_rational) { print STDERR __LINE__, ": epsilon=$epsilon, official=$official, diff=", abs($parts[0] - $official), "\n" if DEBUG; if (abs($parts[0] - $official) < $epsilon) { $table = $utf8::nv_floating_to_rational{$official}; last; } } # Quit if didn't find one. if (! defined $table) { pop @recursed if @recursed; return $type; } } } print STDERR __LINE__, ": $property=$table\n" if DEBUG; } } # Combine lhs (if any) and rhs to get something that matches # the syntax of the lookups. $property_and_table = "$prefix$table"; print STDERR __LINE__, ": $property_and_table\n" if DEBUG; # First try stricter matching. $file = $utf8::stricter_to_file_of{$property_and_table}; # If didn't find it, try again with looser matching by editing # out the applicable characters on the rhs and looking up # again. my $strict_property_and_table; if (! defined $file) { # This isn't used unless the name begins with 'to' $strict_property_and_table = $property_and_table =~ s/^to//r; $table = _loose_name($table); $property_and_table = "$prefix$table"; print STDERR __LINE__, ": $property_and_table\n" if DEBUG; $file = $utf8::loose_to_file_of{$property_and_table}; } # Add the constant and go fetch it in. if (defined $file) { # If the file name contains a !, it means to invert. The # 0+ makes sure result is numeric $invert_it = 0 + $file =~ s/!//; if ($utf8::why_deprecated{$file}) { warnings::warnif('deprecated', "Use of '$type' in \\p{} or \\P{} is deprecated because: $utf8::why_deprecated{$file};"); } if ($caseless && exists $utf8::caseless_equivalent{$property_and_table}) { $file = $utf8::caseless_equivalent{$property_and_table}; } # The pseudo-directory '#' means that there really isn't a # file to read, the data is in-line as part of the string; # we extract it below. $file = "$unicore_dir/lib/$file.pl" unless $file =~ m!^#/!; last GETFILE; } print STDERR __LINE__, ": didn't find $property_and_table\n" if DEBUG; ## ## Last attempt -- see if it's a standard "To" name ## (e.g. "ToLower") ToTitle is used by ucfirst(). ## The user-level way to access ToDigit() and ToFold() ## is to use Unicode::UCD. ## # Only check if caller wants non-binary if ($minbits != 1) { if ($property_and_table =~ s/^to//) { # Look input up in list of properties for which we have # mapping files. First do it with the strict approach if (defined ($file = $utf8::strict_property_to_file_of{ $strict_property_and_table})) { $type = $utf8::file_to_swash_name{$file}; print STDERR __LINE__, ": type set to $type\n" if DEBUG; $file = "$unicore_dir/$file.pl"; last GETFILE; } elsif (defined ($file = $utf8::loose_property_to_file_of{$property_and_table})) { $type = $utf8::file_to_swash_name{$file}; print STDERR __LINE__, ": type set to $type\n" if DEBUG; $file = "$unicore_dir/$file.pl"; last GETFILE; } # If that fails see if there is a corresponding binary # property file elsif (defined ($file = $utf8::loose_to_file_of{$property_and_table})) { # Here, there is no map file for the property we # are trying to get the map of, but this is a # binary property, and there is a file for it that # can easily be translated to a mapping, so use # that, treating this as a binary property. # Setting 'minbits' here causes it to be stored as # such in the cache, so if someone comes along # later looking for just a binary, they get it. $minbits = 1; # The 0+ makes sure is numeric $invert_it = 0 + $file =~ s/!//; $file = "$unicore_dir/lib/$file.pl" unless $file =~ m!^#/!; last GETFILE; } } } ## ## If we reach this line, it's because we couldn't figure ## out what to do with $type. Ouch. ## pop @recursed if @recursed; return $type; } # end of GETFILE block if (defined $file) { print STDERR __LINE__, ": found it (file='$file')\n" if DEBUG; ## ## If we reach here, it was due to a 'last GETFILE' above ## (exception: user-defined properties and mappings), so we ## have a filename, so now we load it if we haven't already. # The pseudo-directory '#' means the result isn't really a # file, but is in-line, with semi-colons to be turned into # new-lines. Since it is in-line there is no advantage to # caching the result if ($file =~ s!^#/!!) { $list = $utf8::inline_definitions[$file]; } else { # Here, we have an actual file to read in and load, but it # may already have been read-in and cached. The cache key # is the class and file to load, and whether the results # need to be inverted. my $found = $Cache{$class, $file, $invert_it}; if ($found and ref($found) eq $class) { print STDERR __LINE__, ": Returning cached swash for '$class,$file,$invert_it' for \\p{$type}\n" if DEBUG; pop @recursed if @recursed; return $found; } local $@; local $!; $list = do $file; die $@ if $@; } $list_is_from_mktables = 1; } } # End of $type is non-null # Here, either $type was null, or we found the requested property and # read it into $list my $extras = ""; my $bits = $minbits; # mktables lists don't have extras, like '&utf8::prop', so don't need # to separate them; also lists are already sorted, so don't need to do # that. if ($list && ! $list_is_from_mktables) { my $taint = substr($list,0,0); # maintain taint # Separate the extras from the code point list, and make sure # user-defined properties and tr/// are well-behaved for # downstream code. if ($user_defined || $none) { my @tmp = split(/^/m, $list); my %seen; no warnings; # The extras are anything that doesn't begin with a hex digit. $extras = join '', $taint, grep /^[^0-9a-fA-F]/, @tmp; # Remove the extras, and sort the remaining entries by the # numeric value of their beginning hex digits, removing any # duplicates. $list = join '', $taint, map { $_->[1] } sort { $a->[0] <=> $b->[0] } map { /^([0-9a-fA-F]+)/ && !$seen{$1}++ ? [ CORE::hex($1), $_ ] : () } @tmp; # XXX doesn't do ranges right } else { # mktables has gone to some trouble to make non-user defined # properties well-behaved, so we can skip the effort we do for # user-defined ones. Any extras are at the very beginning of # the string. # This regex splits out the first lines of $list into $1 and # strips them off from $list, until we get one that begins # with a hex number, alone on the line, or followed by a tab. # Either portion may be empty. $list =~ s/ \A ( .*? ) (?: \z | (?= ^ [0-9a-fA-F]+ (?: \t | $) ) ) //msx; $extras = "$taint$1"; } } if ($none) { my $hextra = sprintf "%04x", $none + 1; $list =~ s/\tXXXX$/\t$hextra/mg; } if ($minbits != 1 && $minbits < 32) { # not binary property my $top = 0; while ($list =~ /^([0-9a-fA-F]+)(?:[\t]([0-9a-fA-F]+)?)(?:[ \t]([0-9a-fA-F]+))?/mg) { my $min = CORE::hex $1; my $max = defined $2 ? CORE::hex $2 : $min; my $val = defined $3 ? CORE::hex $3 : 0; $val += $max - $min if defined $3; $top = $val if $val > $top; } my $topbits = $top > 0xffff ? 32 : $top > 0xff ? 16 : 8; $bits = $topbits if $bits < $topbits; } my @extras; if ($extras) { for my $x ($extras) { my $taint = substr($x,0,0); # maintain taint pos $x = 0; while ($x =~ /^([^0-9a-fA-F\n])(.*)/mg) { my $char = "$1$taint"; my $name = "$2$taint"; print STDERR __LINE__, ": char [$char] => name [$name]\n" if DEBUG; if ($char =~ /[-+!&]/) { my ($c,$t) = split(/::/, $name, 2); # bogus use of ::, really my $subobj; if ($c eq 'utf8') { $subobj = utf8->SWASHNEW($t, "", $minbits, 0); } elsif (exists &$name) { $subobj = utf8->SWASHNEW($name, "", $minbits, 0); } elsif ($c =~ /^([0-9a-fA-F]+)/) { $subobj = utf8->SWASHNEW("", $c, $minbits, 0); } print STDERR __LINE__, ": returned from getting sub object for $name\n" if DEBUG; if (! ref $subobj) { pop @recursed if @recursed && $type; return $subobj; } push @extras, $name => $subobj; $bits = $subobj->{BITS} if $bits < $subobj->{BITS}; $user_defined = $subobj->{USER_DEFINED} if $subobj->{USER_DEFINED}; } } } } if (DEBUG) { print STDERR __LINE__, ": CLASS = $class, TYPE => $type, BITS => $bits, NONE => $none, INVERT_IT => $invert_it, USER_DEFINED => $user_defined"; print STDERR "\nLIST =>\n$list" if defined $list; print STDERR "\nEXTRAS =>\n$extras" if defined $extras; print STDERR "\n"; } my $SWASH = bless { TYPE => $type, BITS => $bits, EXTRAS => $extras, LIST => $list, NONE => $none, USER_DEFINED => $user_defined, @extras, } => $class; if ($file) { $Cache{$class, $file, $invert_it} = $SWASH; if ($type && exists $utf8::SwashInfo{$type} && exists $utf8::SwashInfo{$type}{'specials_name'}) { my $specials_name = $utf8::SwashInfo{$type}{'specials_name'}; no strict "refs"; print STDERR "\nspecials_name => $specials_name\n" if DEBUG; $SWASH->{'SPECIALS'} = \%$specials_name; } $SWASH->{'INVERT_IT'} = $invert_it; } pop @recursed if @recursed && $type; return $SWASH; } } # Now SWASHGET is recasted into a C function S_swatch_get (see utf8.c). 1; use 5.006_001; # for (defined ref) and $#$v and our package Dumpvalue; use strict; our $VERSION = '1.18'; our(%address, $stab, @stab, %stab, %subs); sub ASCII { return ord('A') == 65; } # This module will give incorrect results for some inputs on EBCDIC platforms # before v5.8 *to_native = ($] lt "5.008") ? sub { return shift } : sub { return utf8::unicode_to_native(shift) }; my $APC = chr to_native(0x9F); my $backslash_c_question = (ASCII) ? '\177' : $APC; # documentation nits, handle complex data structures better by chromatic # translate control chars to ^X - Randal Schwartz # Modifications to print types by Peter Gordon v1.0 # Ilya Zakharevich -- patches after 5.001 (and some before ;-) # Won't dump symbol tables and contents of debugged files by default # (IZ) changes for objectification: # c) quote() renamed to method set_quote(); # d) unctrlSet() renamed to method set_unctrl(); # f) Compiles with 'use strict', but in two places no strict refs is needed: # maybe more problems are waiting... my %defaults = ( globPrint => 0, printUndef => 1, tick => "auto", unctrl => 'quote', subdump => 1, dumpReused => 0, bareStringify => 1, hashDepth => '', arrayDepth => '', dumpDBFiles => '', dumpPackages => '', quoteHighBit => '', usageOnly => '', compactDump => '', veryCompact => '', stopDbSignal => '', ); sub new { my $class = shift; my %opt = (%defaults, @_); bless \%opt, $class; } sub set { my $self = shift; my %opt = @_; @$self{keys %opt} = values %opt; } sub get { my $self = shift; wantarray ? @$self{@_} : $$self{pop @_}; } sub dumpValue { my $self = shift; die "usage: \$dumper->dumpValue(value)" unless @_ == 1; local %address; local $^W=0; (print "undef\n"), return unless defined $_[0]; (print $self->stringify($_[0]), "\n"), return unless ref $_[0]; $self->unwrap($_[0],0); } sub dumpValues { my $self = shift; local %address; local $^W=0; (print "undef\n"), return unless defined $_[0]; $self->unwrap(\@_,0); } # This one is good for variable names: sub unctrl { local($_) = @_; return \$_ if ref \$_ eq "GLOB"; s/([\000-\037])/'^' . chr(to_native(ord($1)^64))/eg; s/ $backslash_c_question /^?/xg; $_; } sub stringify { my $self = shift; local $_ = shift; my $noticks = shift; my $tick = $self->{tick}; return 'undef' unless defined $_ or not $self->{printUndef}; return $_ . "" if ref \$_ eq 'GLOB'; { no strict 'refs'; $_ = &{'overload::StrVal'}($_) if $self->{bareStringify} and ref $_ and %overload:: and defined &{'overload::StrVal'}; } if ($tick eq 'auto') { if (/[^[:^cntrl:]\n]/) { # All ASCII controls but \n get '"' $tick = '"'; } else { $tick = "'"; } } if ($tick eq "'") { s/([\'\\])/\\$1/g; } elsif ($self->{unctrl} eq 'unctrl') { s/([\"\\])/\\$1/g ; $_ = &unctrl($_); s/([[:^ascii:]])/'\\0x'.sprintf('%2X',ord($1))/eg if $self->{quoteHighBit}; } elsif ($self->{unctrl} eq 'quote') { s/([\"\\\$\@])/\\$1/g if $tick eq '"'; s/\e/\\e/g; s/([\000-\037$backslash_c_question])/'\\c'._escaped_ord($1)/eg; } s/([[:^ascii:]])/'\\'.sprintf('%3o',ord($1))/eg if $self->{quoteHighBit}; ($noticks || /^\d+(\.\d*)?\Z/) ? $_ : $tick . $_ . $tick; } # Ensure a resulting \ is escaped to be \\ sub _escaped_ord { my $chr = shift; if ($chr eq $backslash_c_question) { $chr = '?'; } else { $chr = chr(to_native(ord($chr)^64)); $chr =~ s{\\}{\\\\}g; } return $chr; } sub DumpElem { my ($self, $v) = (shift, shift); my $short = $self->stringify($v, ref $v); my $shortmore = ''; if ($self->{veryCompact} && ref $v && (ref $v eq 'ARRAY' and !grep(ref $_, @$v) )) { my $depth = $#$v; ($shortmore, $depth) = (' ...', $self->{arrayDepth} - 1) if $self->{arrayDepth} and $depth >= $self->{arrayDepth}; my @a = map $self->stringify($_), @$v[0..$depth]; print "0..$#{$v} @a$shortmore\n"; } elsif ($self->{veryCompact} && ref $v && (ref $v eq 'HASH') and !grep(ref $_, values %$v)) { my @a = sort keys %$v; my $depth = $#a; ($shortmore, $depth) = (' ...', $self->{hashDepth} - 1) if $self->{hashDepth} and $depth >= $self->{hashDepth}; my @b = map {$self->stringify($_) . " => " . $self->stringify($$v{$_})} @a[0..$depth]; local $" = ', '; print "@b$shortmore\n"; } else { print "$short\n"; $self->unwrap($v,shift); } } sub unwrap { my $self = shift; return if $DB::signal and $self->{stopDbSignal}; my ($v) = shift ; my ($s) = shift ; # extra no of spaces my $sp; my (%v,@v,$address,$short,$fileno); $sp = " " x $s ; $s += 3 ; # Check for reused addresses if (ref $v) { my $val = $v; { no strict 'refs'; $val = &{'overload::StrVal'}($v) if %overload:: and defined &{'overload::StrVal'}; } ($address) = $val =~ /(0x[0-9a-f]+)\)$/ ; if (!$self->{dumpReused} && defined $address) { $address{$address}++ ; if ( $address{$address} > 1 ) { print "${sp}-> REUSED_ADDRESS\n" ; return ; } } } elsif (ref \$v eq 'GLOB') { $address = "$v" . ""; # To avoid a bug with globs $address{$address}++ ; if ( $address{$address} > 1 ) { print "${sp}*DUMPED_GLOB*\n" ; return ; } } if (ref $v eq 'Regexp') { my $re = "$v"; $re =~ s,/,\\/,g; print "$sp-> qr/$re/\n"; return; } if ( UNIVERSAL::isa($v, 'HASH') ) { my @sortKeys = sort keys(%$v) ; my $more; my $tHashDepth = $#sortKeys ; $tHashDepth = $#sortKeys < $self->{hashDepth}-1 ? $#sortKeys : $self->{hashDepth}-1 unless $self->{hashDepth} eq '' ; $more = "....\n" if $tHashDepth < $#sortKeys ; my $shortmore = ""; $shortmore = ", ..." if $tHashDepth < $#sortKeys ; $#sortKeys = $tHashDepth ; if ($self->{compactDump} && !grep(ref $_, values %{$v})) { $short = $sp; my @keys; for (@sortKeys) { push @keys, $self->stringify($_) . " => " . $self->stringify($v->{$_}); } $short .= join ', ', @keys; $short .= $shortmore; (print "$short\n"), return if length $short <= $self->{compactDump}; } for my $key (@sortKeys) { return if $DB::signal and $self->{stopDbSignal}; my $value = $ {$v}{$key} ; print $sp, $self->stringify($key), " => "; $self->DumpElem($value, $s); } print "$sp empty hash\n" unless @sortKeys; print "$sp$more" if defined $more ; } elsif ( UNIVERSAL::isa($v, 'ARRAY') ) { my $tArrayDepth = $#{$v} ; my $more ; $tArrayDepth = $#$v < $self->{arrayDepth}-1 ? $#$v : $self->{arrayDepth}-1 unless $self->{arrayDepth} eq '' ; $more = "....\n" if $tArrayDepth < $#{$v} ; my $shortmore = ""; $shortmore = " ..." if $tArrayDepth < $#{$v} ; if ($self->{compactDump} && !grep(ref $_, @{$v})) { if ($#$v >= 0) { $short = $sp . "0..$#{$v} " . join(" ", map {exists $v->[$_] ? $self->stringify($v->[$_]) : "empty"} (0..$tArrayDepth) ) . "$shortmore"; } else { $short = $sp . "empty array"; } (print "$short\n"), return if length $short <= $self->{compactDump}; } for my $num (0 .. $tArrayDepth) { return if $DB::signal and $self->{stopDbSignal}; print "$sp$num "; if (exists $v->[$num]) { $self->DumpElem($v->[$num], $s); } else { print "empty slot\n"; } } print "$sp empty array\n" unless @$v; print "$sp$more" if defined $more ; } elsif ( UNIVERSAL::isa($v, 'SCALAR') or ref $v eq 'REF' ) { print "$sp-> "; $self->DumpElem($$v, $s); } elsif ( UNIVERSAL::isa($v, 'CODE') ) { print "$sp-> "; $self->dumpsub(0, $v); } elsif ( UNIVERSAL::isa($v, 'GLOB') ) { print "$sp-> ",$self->stringify($$v,1),"\n"; if ($self->{globPrint}) { $s += 3; $self->dumpglob('', $s, "{$$v}", $$v, 1); } elsif (defined ($fileno = fileno($v))) { print( (' ' x ($s+3)) . "FileHandle({$$v}) => fileno($fileno)\n" ); } } elsif (ref \$v eq 'GLOB') { if ($self->{globPrint}) { $self->dumpglob('', $s, "{$v}", $v, 1); } elsif (defined ($fileno = fileno(\$v))) { print( (' ' x $s) . "FileHandle({$v}) => fileno($fileno)\n" ); } } } sub matchvar { $_[0] eq $_[1] or ($_[1] =~ /^([!~])(.)([\x00-\xff]*)/) and ($1 eq '!') ^ (eval {($_[2] . "::" . $_[0]) =~ /$2$3/}); } sub compactDump { my $self = shift; $self->{compactDump} = shift if @_; $self->{compactDump} = 6*80-1 if $self->{compactDump} and $self->{compactDump} < 2; $self->{compactDump}; } sub veryCompact { my $self = shift; $self->{veryCompact} = shift if @_; $self->compactDump(1) if !$self->{compactDump} and $self->{veryCompact}; $self->{veryCompact}; } sub set_unctrl { my $self = shift; if (@_) { my $in = shift; if ($in eq 'unctrl' or $in eq 'quote') { $self->{unctrl} = $in; } else { print "Unknown value for 'unctrl'.\n"; } } $self->{unctrl}; } sub set_quote { my $self = shift; if (@_ and $_[0] eq '"') { $self->{tick} = '"'; $self->{unctrl} = 'quote'; } elsif (@_ and $_[0] eq 'auto') { $self->{tick} = 'auto'; $self->{unctrl} = 'quote'; } elsif (@_) { # Need to set $self->{tick} = "'"; $self->{unctrl} = 'unctrl'; } $self->{tick}; } sub dumpglob { my $self = shift; return if $DB::signal and $self->{stopDbSignal}; my ($package, $off, $key, $val, $all) = @_; local(*stab) = $val; my $fileno; if (($key !~ /^_{dumpDBFiles}) and defined $stab) { print( (' ' x $off) . "\$", &unctrl($key), " = " ); $self->DumpElem($stab, 3+$off); } if (($key !~ /^_{dumpDBFiles}) and @stab) { print( (' ' x $off) . "\@$key = (\n" ); $self->unwrap(\@stab,3+$off) ; print( (' ' x $off) . ")\n" ); } if ($key ne "main::" && $key ne "DB::" && %stab && ($self->{dumpPackages} or $key !~ /::$/) && ($key !~ /^_{dumpDBFiles}) && !($package eq "Dumpvalue" and $key eq "stab")) { print( (' ' x $off) . "\%$key = (\n" ); $self->unwrap(\%stab,3+$off) ; print( (' ' x $off) . ")\n" ); } if (defined ($fileno = fileno(*stab))) { print( (' ' x $off) . "FileHandle($key) => fileno($fileno)\n" ); } if ($all) { if (defined &stab) { $self->dumpsub($off, $key); } } } sub CvGV_name { my $self = shift; my $in = shift; return if $self->{skipCvGV}; # Backdoor to avoid problems if XS broken... $in = \&$in; # Hard reference... eval {require Devel::Peek; 1} or return; my $gv = Devel::Peek::CvGV($in) or return; *$gv{PACKAGE} . '::' . *$gv{NAME}; } sub dumpsub { my $self = shift; my ($off,$sub) = @_; my $ini = $sub; my $s; $sub = $1 if $sub =~ /^\{\*(.*)\}$/; my $subref = defined $1 ? \&$sub : \&$ini; my $place = $DB::sub{$sub} || (($s = $subs{"$subref"}) && $DB::sub{$s}) || (($s = $self->CvGV_name($subref)) && $DB::sub{$s}) || ($self->{subdump} && ($s = $self->findsubs("$subref")) && $DB::sub{$s}); $s = $sub unless defined $s; $place = '???' unless defined $place; print( (' ' x $off) . "&$s in $place\n" ); } sub findsubs { my $self = shift; return undef unless %DB::sub; my ($addr, $name, $loc); while (($name, $loc) = each %DB::sub) { $addr = \&$name; $subs{"$addr"} = $name; } $self->{subdump} = 0; $subs{ shift() }; } sub dumpvars { my $self = shift; my ($package,@vars) = @_; local(%address,$^W); my ($key,$val); $package .= "::" unless $package =~ /::$/; *stab = *main::; while ($package =~ /(\w+?::)/g) { *stab = $ {stab}{$1}; } $self->{TotalStrings} = 0; $self->{Strings} = 0; $self->{CompleteTotal} = 0; while (($key,$val) = each(%stab)) { return if $DB::signal and $self->{stopDbSignal}; next if @vars && !grep( matchvar($key, $_), @vars ); if ($self->{usageOnly}) { $self->globUsage(\$val, $key) if ($package ne 'Dumpvalue' or $key ne 'stab') and ref(\$val) eq 'GLOB'; } else { $self->dumpglob($package, 0,$key, $val); } } if ($self->{usageOnly}) { print <{TotalStrings} bytes in $self->{Strings} strings. EOP $self->{CompleteTotal} += $self->{TotalStrings}; print <{CompleteTotal} bytes (1 level deep) + overhead. EOP } } sub scalarUsage { my $self = shift; my $size; if (UNIVERSAL::isa($_[0], 'ARRAY')) { $size = $self->arrayUsage($_[0]); } elsif (UNIVERSAL::isa($_[0], 'HASH')) { $size = $self->hashUsage($_[0]); } elsif (!ref($_[0])) { $size = length($_[0]); } $self->{TotalStrings} += $size; $self->{Strings}++; $size; } sub arrayUsage { # array ref, name my $self = shift; my $size = 0; map {$size += $self->scalarUsage($_)} @{$_[0]}; my $len = @{$_[0]}; print "\@$_[1] = $len item", ($len > 1 ? "s" : ""), " (data: $size bytes)\n" if defined $_[1]; $self->{CompleteTotal} += $size; $size; } sub hashUsage { # hash ref, name my $self = shift; my @keys = keys %{$_[0]}; my @values = values %{$_[0]}; my $keys = $self->arrayUsage(\@keys); my $values = $self->arrayUsage(\@values); my $len = @keys; my $total = $keys + $values; print "\%$_[1] = $len item", ($len > 1 ? "s" : ""), " (keys: $keys; values: $values; total: $total bytes)\n" if defined $_[1]; $total; } sub globUsage { # glob ref, name my $self = shift; local *stab = *{$_[0]}; my $total = 0; $total += $self->scalarUsage($stab) if defined $stab; $total += $self->arrayUsage(\@stab, $_[1]) if @stab; $total += $self->hashUsage(\%stab, $_[1]) if %stab and $_[1] ne "main::" and $_[1] ne "DB::"; #and !($package eq "Dumpvalue" and $key eq "stab")); $total; } 1; package sort; our $VERSION = '2.04'; # The hints for pp_sort are now stored in $^H{sort}; older versions # of perl used the global variable $sort::hints. -- rjh 2005-12-19 $sort::stable_bit = 0x00000100; $sort::unstable_bit = 0x00000200; use strict; sub import { shift; if (@_ == 0) { require Carp; Carp::croak("sort pragma requires arguments"); } local $_; $^H{sort} //= 0; while ($_ = shift(@_)) { if ($_ eq 'stable') { $^H{sort} |= $sort::stable_bit; $^H{sort} &= ~$sort::unstable_bit; } elsif ($_ eq 'defaults') { $^H{sort} = 0; } else { require Carp; Carp::croak("sort: unknown subpragma '$_'"); } } } sub unimport { shift; if (@_ == 0) { require Carp; Carp::croak("sort pragma requires arguments"); } local $_; no warnings 'uninitialized'; # bitops would warn while ($_ = shift(@_)) { if ($_ eq 'stable') { $^H{sort} &= ~$sort::stable_bit; $^H{sort} |= $sort::unstable_bit; } else { require Carp; Carp::croak("sort: unknown subpragma '$_'"); } } } sub current { my @sort; if ($^H{sort}) { push @sort, 'stable' if $^H{sort} & $sort::stable_bit; } join(' ', @sort); } 1; __END__ package AnyDBM_File; use warnings; use strict; use 5.006_001; our $VERSION = '1.01'; our @ISA = qw(NDBM_File DB_File GDBM_File SDBM_File ODBM_File) unless @ISA; my $mod; for $mod (@ISA) { if (eval "require $mod") { @ISA = ($mod); # if we leave @ISA alone, warnings abound return 1; } } die "No DBM package was successfully found or installed"; __END__ # -*- buffer-read-only: t -*- # !!!!!!! DO NOT EDIT THIS FILE !!!!!!! # This file is built by regen/feature.pl. # Any changes made here will be lost! package feature; our $VERSION = '1.52'; our %feature = ( fc => 'feature_fc', say => 'feature_say', state => 'feature_state', switch => 'feature_switch', bitwise => 'feature_bitwise', evalbytes => 'feature_evalbytes', array_base => 'feature_arybase', signatures => 'feature_signatures', current_sub => 'feature___SUB__', refaliasing => 'feature_refaliasing', postderef_qq => 'feature_postderef_qq', unicode_eval => 'feature_unieval', declared_refs => 'feature_myref', unicode_strings => 'feature_unicode', ); our %feature_bundle = ( "5.10" => [qw(array_base say state switch)], "5.11" => [qw(array_base say state switch unicode_strings)], "5.15" => [qw(current_sub evalbytes fc say state switch unicode_eval unicode_strings)], "5.23" => [qw(current_sub evalbytes fc postderef_qq say state switch unicode_eval unicode_strings)], "5.27" => [qw(bitwise current_sub evalbytes fc postderef_qq say state switch unicode_eval unicode_strings)], "all" => [qw(array_base bitwise current_sub declared_refs evalbytes fc postderef_qq refaliasing say signatures state switch unicode_eval unicode_strings)], "default" => [qw(array_base)], ); $feature_bundle{"5.12"} = $feature_bundle{"5.11"}; $feature_bundle{"5.13"} = $feature_bundle{"5.11"}; $feature_bundle{"5.14"} = $feature_bundle{"5.11"}; $feature_bundle{"5.16"} = $feature_bundle{"5.15"}; $feature_bundle{"5.17"} = $feature_bundle{"5.15"}; $feature_bundle{"5.18"} = $feature_bundle{"5.15"}; $feature_bundle{"5.19"} = $feature_bundle{"5.15"}; $feature_bundle{"5.20"} = $feature_bundle{"5.15"}; $feature_bundle{"5.21"} = $feature_bundle{"5.15"}; $feature_bundle{"5.22"} = $feature_bundle{"5.15"}; $feature_bundle{"5.24"} = $feature_bundle{"5.23"}; $feature_bundle{"5.25"} = $feature_bundle{"5.23"}; $feature_bundle{"5.26"} = $feature_bundle{"5.23"}; $feature_bundle{"5.28"} = $feature_bundle{"5.27"}; $feature_bundle{"5.9.5"} = $feature_bundle{"5.10"}; my %noops = ( postderef => 1, lexical_subs => 1, ); our $hint_shift = 26; our $hint_mask = 0x1c000000; our @hint_bundles = qw( default 5.10 5.11 5.15 5.23 5.27 ); # This gets set (for now) in $^H as well as in %^H, # for runtime speed of the uc/lc/ucfirst/lcfirst functions. # See HINT_UNI_8_BIT in perl.h. our $hint_uni8bit = 0x00000800; # TODO: # - think about versioned features (use feature switch => 2) sub import { shift; if (!@_) { croak("No features specified"); } __common(1, @_); } sub unimport { shift; # A bare C should reset to the default bundle if (!@_) { $^H &= ~($hint_uni8bit|$hint_mask); return; } __common(0, @_); } sub __common { my $import = shift; my $bundle_number = $^H & $hint_mask; my $features = $bundle_number != $hint_mask && $feature_bundle{$hint_bundles[$bundle_number >> $hint_shift]}; if ($features) { # Features are enabled implicitly via bundle hints. # Delete any keys that may be left over from last time. delete @^H{ values(%feature) }; $^H |= $hint_mask; for (@$features) { $^H{$feature{$_}} = 1; $^H |= $hint_uni8bit if $_ eq 'unicode_strings'; } } while (@_) { my $name = shift; if (substr($name, 0, 1) eq ":") { my $v = substr($name, 1); if (!exists $feature_bundle{$v}) { $v =~ s/^([0-9]+)\.([0-9]+).[0-9]+$/$1.$2/; if (!exists $feature_bundle{$v}) { unknown_feature_bundle(substr($name, 1)); } } unshift @_, @{$feature_bundle{$v}}; next; } if (!exists $feature{$name}) { if (exists $noops{$name}) { next; } unknown_feature($name); } if ($import) { $^H{$feature{$name}} = 1; $^H |= $hint_uni8bit if $name eq 'unicode_strings'; } else { delete $^H{$feature{$name}}; $^H &= ~ $hint_uni8bit if $name eq 'unicode_strings'; } } } sub unknown_feature { my $feature = shift; croak(sprintf('Feature "%s" is not supported by Perl %vd', $feature, $^V)); } sub unknown_feature_bundle { my $feature = shift; croak(sprintf('Feature bundle "%s" is not supported by Perl %vd', $feature, $^V)); } sub croak { require Carp; Carp::croak(@_); } 1; # ex: set ro: package subs; our $VERSION = '1.03'; require 5.000; sub import { my $callpack = caller; my $pack = shift; my @imports = @_; foreach my $sym (@imports) { *{"${callpack}::$sym"} = \&{"${callpack}::$sym"}; } }; 1; package SelfLoader; use 5.008; use strict; use IO::Handle; our $VERSION = "1.25"; # The following bit of eval-magic is necessary to make this work on # perls < 5.009005. our $AttrList; BEGIN { if ($] > 5.009004) { eval <<'NEWERPERL'; use 5.009005; # due to new regexp features # allow checking for valid ': attrlist' attachments # see also AutoSplit $AttrList = qr{ \s* : \s* (?: # one attribute (?> # no backtrack (?! \d) \w+ (? \( (?: [^()]++ | (?&nested)++ )*+ \) ) ? ) (?: \s* : \s* | \s+ (?! :) ) )* }x; NEWERPERL } else { eval <<'OLDERPERL'; # allow checking for valid ': attrlist' attachments # (we use 'our' rather than 'my' here, due to the rather complex and buggy # behaviour of lexicals with qr// and (??{$lex}) ) our $nested; $nested = qr{ \( (?: (?> [^()]+ ) | (??{ $nested }) )* \) }x; our $one_attr = qr{ (?> (?! \d) \w+ (?:$nested)? ) (?:\s*\:\s*|\s+(?!\:)) }x; $AttrList = qr{ \s* : \s* (?: $one_attr )* }x; OLDERPERL } } use Exporter; our @ISA = qw(Exporter); our @EXPORT = qw(AUTOLOAD); sub Version {$VERSION} sub DEBUG () { 0 } my %Cache; # private cache for all SelfLoader's client packages # in croak and carp, protect $@ from "require Carp;" RT #40216 sub croak { { local $@; require Carp; } goto &Carp::croak } sub carp { { local $@; require Carp; } goto &Carp::carp } AUTOLOAD { our $AUTOLOAD; print STDERR "SelfLoader::AUTOLOAD for $AUTOLOAD\n" if DEBUG; my $SL_code = $Cache{$AUTOLOAD}; my $save = $@; # evals in both AUTOLOAD and _load_stubs can corrupt $@ unless ($SL_code) { # Maybe this pack had stubs before __DATA__, and never initialized. # Or, this maybe an automatic DESTROY method call when none exists. $AUTOLOAD =~ m/^(.*)::/; SelfLoader->_load_stubs($1) unless exists $Cache{"${1}::_load_stubs((caller)[0]) } sub _load_stubs { # $endlines is used by Devel::SelfStubber to capture lines after __END__ my($self, $callpack, $endlines) = @_; no strict "refs"; my $fh = \*{"${callpack}::DATA"}; use strict; my $currpack = $callpack; my($line,$name,@lines, @stubs, $protoype); print STDERR "SelfLoader::load_stubs($callpack)\n" if DEBUG; croak("$callpack doesn't contain an __DATA__ token") unless defined fileno($fh); # Protect: fork() shares the file pointer between the parent and the kid if(sysseek($fh, tell($fh), 0)) { open my $nfh, '<&', $fh or croak "reopen: $!";# dup() the fd close $fh or die "close: $!"; # autocloses, but be # paranoid open $fh, '<&', $nfh or croak "reopen2: $!"; # dup() the fd "back" close $nfh or die "close after reopen: $!"; # autocloses, but be # paranoid $fh->untaint; } $Cache{"${currpack}::) and $line !~ m/^__END__/) { if ($line =~ m/ ^\s* # indentation sub\s+([\w:]+)\s* # 'sub' and sub name ( (?:\([\\\$\@\%\&\*\;]*\))? # optional prototype sigils (?:$AttrList)? # optional attribute list )/x) { push(@stubs, $self->_add_to_cache($name, $currpack, \@lines, $protoype)); $protoype = $2; @lines = ($line); if (index($1,'::') == -1) { # simple sub name $name = "${currpack}::$1"; } else { # sub name with package $name = $1; $name =~ m/^(.*)::/; if (defined(&{"${1}::AUTOLOAD"})) { \&{"${1}::AUTOLOAD"} == \&SelfLoader::AUTOLOAD || die 'SelfLoader Error: attempt to specify Selfloading', " sub $name in non-selfloading module $1"; } else { $self->export($1,'AUTOLOAD'); } } } elsif ($line =~ m/^package\s+([\w:]+)/) { # A package declared push(@stubs, $self->_add_to_cache($name, $currpack, \@lines, $protoype)); $self->_package_defined($line); $name = ''; @lines = (); $currpack = $1; $Cache{"${currpack}::export($currpack,'AUTOLOAD'); } } else { push(@lines,$line); } } if (defined($line) && $line =~ /^__END__/) { # __END__ unless ($line =~ /^__END__\s*DATA/) { if ($endlines) { # Devel::SelfStubber would like us to capture the lines after # __END__ so it can write out the entire file @$endlines = <$fh>; } close($fh); } } push(@stubs, $self->_add_to_cache($name, $currpack, \@lines, $protoype)); no strict; eval join('', @stubs) if @stubs; } sub _add_to_cache { my($self,$fullname,$pack,$lines, $protoype) = @_; return () unless $fullname; carp("Redefining sub $fullname") if exists $Cache{$fullname}; $Cache{$fullname} = join('', "\n\#line 1 \"sub $fullname\"\npackage $pack; ", @$lines); #$Cache{$fullname} = join('', "package $pack; ",@$lines); print STDERR "SelfLoader cached $fullname: $Cache{$fullname}" if DEBUG; # return stub to be eval'd defined($protoype) ? "sub $fullname $protoype;" : "sub $fullname;" } sub _package_defined {} 1; __END__ package overload; our $VERSION = '1.30'; %ops = ( with_assign => "+ - * / % ** << >> x .", assign => "+= -= *= /= %= **= <<= >>= x= .=", num_comparison => "< <= > >= == !=", '3way_comparison' => "<=> cmp", str_comparison => "lt le gt ge eq ne", binary => '& &= | |= ^ ^= &. &.= |. |.= ^. ^.=', unary => "neg ! ~ ~.", mutators => '++ --', func => "atan2 cos sin exp abs log sqrt int", conversion => 'bool "" 0+ qr', iterators => '<>', filetest => "-X", dereferencing => '${} @{} %{} &{} *{}', matching => '~~', special => 'nomethod fallback =', ); my %ops_seen; @ops_seen{ map split(/ /), values %ops } = (); sub nil {} sub OVERLOAD { $package = shift; my %arg = @_; my $sub; *{$package . "::(("} = \&nil; # Make it findable via fetchmethod. for (keys %arg) { if ($_ eq 'fallback') { for my $sym (*{$package . "::()"}) { *$sym = \&nil; # Make it findable via fetchmethod. $$sym = $arg{$_}; } } else { warnings::warnif("overload arg '$_' is invalid") unless exists $ops_seen{$_}; $sub = $arg{$_}; if (not ref $sub) { $ {$package . "::(" . $_} = $sub; $sub = \&nil; } #print STDERR "Setting '$ {'package'}::\cO$_' to \\&'$sub'.\n"; *{$package . "::(" . $_} = \&{ $sub }; } } } sub import { $package = (caller())[0]; # *{$package . "::OVERLOAD"} = \&OVERLOAD; shift; $package->overload::OVERLOAD(@_); } sub unimport { $package = (caller())[0]; shift; *{$package . "::(("} = \&nil; for (@_) { warnings::warnif("overload arg '$_' is invalid") unless exists $ops_seen{$_}; delete $ {$package . "::"}{$_ eq 'fallback' ? '()' : "(" .$_}; } } sub Overloaded { my $package = shift; $package = ref $package if ref $package; mycan ($package, '()') || mycan ($package, '(('); } sub ov_method { my $globref = shift; return undef unless $globref; my $sub = \&{*$globref}; no overloading; return $sub if $sub != \&nil; return shift->can($ {*$globref}); } sub OverloadedStringify { my $package = shift; $package = ref $package if ref $package; #$package->can('(""') ov_method mycan($package, '(""'), $package or ov_method mycan($package, '(0+'), $package or ov_method mycan($package, '(bool'), $package or ov_method mycan($package, '(nomethod'), $package; } sub Method { my $package = shift; if(ref $package) { local $@; local $!; require Scalar::Util; $package = Scalar::Util::blessed($package); return undef if !defined $package; } #my $meth = $package->can('(' . shift); ov_method mycan($package, '(' . shift), $package; #return $meth if $meth ne \&nil; #return $ {*{$meth}}; } sub AddrRef { no overloading; "$_[0]"; } *StrVal = *AddrRef; sub mycan { # Real can would leave stubs. my ($package, $meth) = @_; local $@; local $!; require mro; my $mro = mro::get_linear_isa($package); foreach my $p (@$mro) { my $fqmeth = $p . q{::} . $meth; return \*{$fqmeth} if defined &{$fqmeth}; } return undef; } %constants = ( 'integer' => 0x1000, # HINT_NEW_INTEGER 'float' => 0x2000, # HINT_NEW_FLOAT 'binary' => 0x4000, # HINT_NEW_BINARY 'q' => 0x8000, # HINT_NEW_STRING 'qr' => 0x10000, # HINT_NEW_RE ); use warnings::register; sub constant { # Arguments: what, sub while (@_) { if (@_ == 1) { warnings::warnif ("Odd number of arguments for overload::constant"); last; } elsif (!exists $constants {$_ [0]}) { warnings::warnif ("'$_[0]' is not an overloadable type"); } elsif (!ref $_ [1] || "$_[1]" !~ /(^|=)CODE\(0x[0-9a-f]+\)$/) { # Can't use C above as code references can be # blessed, and C would return the package the ref is blessed into. if (warnings::enabled) { $_ [1] = "undef" unless defined $_ [1]; warnings::warn ("'$_[1]' is not a code reference"); } } else { $^H{$_[0]} = $_[1]; $^H |= $constants{$_[0]}; } shift, shift; } } sub remove_constant { # Arguments: what, sub while (@_) { delete $^H{$_[0]}; $^H &= ~ $constants{$_[0]}; shift, shift; } } 1; __END__ package strict; $strict::VERSION = "1.11"; my ( %bitmask, %explicit_bitmask ); BEGIN { # Verify that we're called correctly so that strictures will work. # Can't use Carp, since Carp uses us! # see also warnings.pm. die sprintf "Incorrect use of pragma '%s' at %s line %d.\n", __PACKAGE__, +(caller)[1,2] if __FILE__ !~ ( '(?x) \b '.__PACKAGE__.' \.pmc? \z' ) && __FILE__ =~ ( '(?x) \b (?i:'.__PACKAGE__.') \.pmc? \z' ); %bitmask = ( refs => 0x00000002, subs => 0x00000200, vars => 0x00000400, ); %explicit_bitmask = ( refs => 0x00000020, subs => 0x00000040, vars => 0x00000080, ); my $bits = 0; $bits |= $_ for values %bitmask; my $inline_all_bits = $bits; *all_bits = sub () { $inline_all_bits }; $bits = 0; $bits |= $_ for values %explicit_bitmask; my $inline_all_explicit_bits = $bits; *all_explicit_bits = sub () { $inline_all_explicit_bits }; } sub bits { my $bits = 0; my @wrong; foreach my $s (@_) { if (exists $bitmask{$s}) { $^H |= $explicit_bitmask{$s}; $bits |= $bitmask{$s}; } else { push @wrong, $s; } } if (@wrong) { require Carp; Carp::croak("Unknown 'strict' tag(s) '@wrong'"); } $bits; } sub import { shift; $^H |= @_ ? &bits : all_bits | all_explicit_bits; } sub unimport { shift; if (@_) { $^H &= ~&bits; } else { $^H &= ~all_bits; $^H |= all_explicit_bits; } } 1; __END__ package English; our $VERSION = '1.10'; require Exporter; @ISA = qw(Exporter); no warnings; my $globbed_match ; # Grandfather $NAME import sub import { my $this = shift; my @list = grep { ! /^-no_match_vars$/ } @_ ; local $Exporter::ExportLevel = 1; if ( @_ == @list ) { *EXPORT = \@COMPLETE_EXPORT ; $globbed_match ||= ( eval q{ *MATCH = *& ; *PREMATCH = *` ; *POSTMATCH = *' ; 1 ; } || do { require Carp ; Carp::croak("Can't create English for match leftovers: $@") ; } ) ; } else { *EXPORT = \@MINIMAL_EXPORT ; } Exporter::import($this,grep {s/^\$/*/} @list); } @MINIMAL_EXPORT = qw( *ARG *LAST_PAREN_MATCH *INPUT_LINE_NUMBER *NR *INPUT_RECORD_SEPARATOR *RS *OUTPUT_AUTOFLUSH *OUTPUT_FIELD_SEPARATOR *OFS *OUTPUT_RECORD_SEPARATOR *ORS *LIST_SEPARATOR *SUBSCRIPT_SEPARATOR *SUBSEP *FORMAT_PAGE_NUMBER *FORMAT_LINES_PER_PAGE *FORMAT_LINES_LEFT *FORMAT_NAME *FORMAT_TOP_NAME *FORMAT_LINE_BREAK_CHARACTERS *FORMAT_FORMFEED *CHILD_ERROR *OS_ERROR *ERRNO *EXTENDED_OS_ERROR *EVAL_ERROR *PROCESS_ID *PID *REAL_USER_ID *UID *EFFECTIVE_USER_ID *EUID *REAL_GROUP_ID *GID *EFFECTIVE_GROUP_ID *EGID *PROGRAM_NAME *PERL_VERSION *OLD_PERL_VERSION *ACCUMULATOR *COMPILING *DEBUGGING *SYSTEM_FD_MAX *INPLACE_EDIT *PERLDB *BASETIME *WARNING *EXECUTABLE_NAME *OSNAME *LAST_REGEXP_CODE_RESULT *EXCEPTIONS_BEING_CAUGHT *LAST_SUBMATCH_RESULT @LAST_MATCH_START @LAST_MATCH_END ); @MATCH_EXPORT = qw( *MATCH *PREMATCH *POSTMATCH ); @COMPLETE_EXPORT = ( @MINIMAL_EXPORT, @MATCH_EXPORT ) ; # The ground of all being. *ARG = *_ ; # Matching. *LAST_PAREN_MATCH = *+ ; *LAST_SUBMATCH_RESULT = *^N ; *LAST_MATCH_START = *-{ARRAY} ; *LAST_MATCH_END = *+{ARRAY} ; # Input. *INPUT_LINE_NUMBER = *. ; *NR = *. ; *INPUT_RECORD_SEPARATOR = */ ; *RS = */ ; # Output. *OUTPUT_AUTOFLUSH = *| ; *OUTPUT_FIELD_SEPARATOR = *, ; *OFS = *, ; *OUTPUT_RECORD_SEPARATOR = *\ ; *ORS = *\ ; # Interpolation "constants". *LIST_SEPARATOR = *" ; *SUBSCRIPT_SEPARATOR = *; ; *SUBSEP = *; ; # Formats *FORMAT_PAGE_NUMBER = *% ; *FORMAT_LINES_PER_PAGE = *= ; *FORMAT_LINES_LEFT = *-{SCALAR} ; *FORMAT_NAME = *~ ; *FORMAT_TOP_NAME = *^ ; *FORMAT_LINE_BREAK_CHARACTERS = *: ; *FORMAT_FORMFEED = *^L ; # Error status. *CHILD_ERROR = *? ; *OS_ERROR = *! ; *ERRNO = *! ; *OS_ERROR = *! ; *ERRNO = *! ; *EXTENDED_OS_ERROR = *^E ; *EVAL_ERROR = *@ ; # Process info. *PROCESS_ID = *$ ; *PID = *$ ; *REAL_USER_ID = *< ; *UID = *< ; *EFFECTIVE_USER_ID = *> ; *EUID = *> ; *REAL_GROUP_ID = *( ; *GID = *( ; *EFFECTIVE_GROUP_ID = *) ; *EGID = *) ; *PROGRAM_NAME = *0 ; # Internals. *PERL_VERSION = *^V ; *OLD_PERL_VERSION = *] ; *ACCUMULATOR = *^A ; *COMPILING = *^C ; *DEBUGGING = *^D ; *SYSTEM_FD_MAX = *^F ; *INPLACE_EDIT = *^I ; *PERLDB = *^P ; *LAST_REGEXP_CODE_RESULT = *^R ; *EXCEPTIONS_BEING_CAUGHT = *^S ; *BASETIME = *^T ; *WARNING = *^W ; *EXECUTABLE_NAME = *^X ; *OSNAME = *^O ; # Deprecated. # *ARRAY_BASE = *[ ; # *OFMT = *# ; 1; package integer; our $VERSION = '1.01'; $integer::hint_bits = 0x1; sub import { $^H |= $integer::hint_bits; } sub unimport { $^H &= ~$integer::hint_bits; } 1; #!perl -w package version; use 5.006002; use strict; use warnings::register; if ($] >= 5.015) { warnings::register_categories(qw/version/); } our $VERSION = 0.9923; our $CLASS = 'version'; our (@ISA, $STRICT, $LAX); # avoid using Exporter require version::regex; *version::is_lax = \&version::regex::is_lax; *version::is_strict = \&version::regex::is_strict; *LAX = \$version::regex::LAX; *LAX_DECIMAL_VERSION = \$version::regex::LAX_DECIMAL_VERSION; *LAX_DOTTED_DECIMAL_VERSION = \$version::regex::LAX_DOTTED_DECIMAL_VERSION; *STRICT = \$version::regex::STRICT; *STRICT_DECIMAL_VERSION = \$version::regex::STRICT_DECIMAL_VERSION; *STRICT_DOTTED_DECIMAL_VERSION = \$version::regex::STRICT_DOTTED_DECIMAL_VERSION; sub import { no strict 'refs'; my ($class) = shift; # Set up any derived class unless ($class eq $CLASS) { local $^W; *{$class.'::declare'} = \&{$CLASS.'::declare'}; *{$class.'::qv'} = \&{$CLASS.'::qv'}; } my %args; if (@_) { # any remaining terms are arguments map { $args{$_} = 1 } @_ } else { # no parameters at all on use line %args = ( qv => 1, 'UNIVERSAL::VERSION' => 1, ); } my $callpkg = caller(); if (exists($args{declare})) { *{$callpkg.'::declare'} = sub {return $class->declare(shift) } unless defined(&{$callpkg.'::declare'}); } if (exists($args{qv})) { *{$callpkg.'::qv'} = sub {return $class->qv(shift) } unless defined(&{$callpkg.'::qv'}); } if (exists($args{'UNIVERSAL::VERSION'})) { local $^W; *UNIVERSAL::VERSION = \&{$CLASS.'::_VERSION'}; } if (exists($args{'VERSION'})) { *{$callpkg.'::VERSION'} = \&{$CLASS.'::_VERSION'}; } if (exists($args{'is_strict'})) { *{$callpkg.'::is_strict'} = \&{$CLASS.'::is_strict'} unless defined(&{$callpkg.'::is_strict'}); } if (exists($args{'is_lax'})) { *{$callpkg.'::is_lax'} = \&{$CLASS.'::is_lax'} unless defined(&{$callpkg.'::is_lax'}); } } 1; package bigint; use 5.010; use strict; use warnings; our $VERSION = '0.49'; use Exporter; our @ISA = qw( Exporter ); our @EXPORT_OK = qw( PI e bpi bexp hex oct ); our @EXPORT = qw( inf NaN ); use overload; ############################################################################## # These are all alike, and thus faked by AUTOLOAD my @faked = qw/round_mode accuracy precision div_scale/; our ($AUTOLOAD, $_lite); # _lite for testsuite sub AUTOLOAD { my $name = $AUTOLOAD; $name =~ s/.*:://; # split package no strict 'refs'; foreach my $n (@faked) { if ($n eq $name) { *{"bigint::$name"} = sub { my $self = shift; no strict 'refs'; if (defined $_[0]) { return Math::BigInt->$name($_[0]); } return Math::BigInt->$name(); }; return &$name; } } # delayed load of Carp and avoid recursion require Carp; Carp::croak ("Can't call bigint\-\>$name, not a valid method"); } sub upgrade { $Math::BigInt::upgrade; } sub _binary_constant { # this takes a binary/hexadecimal/octal constant string and returns it # as string suitable for new. Basically it converts octal to decimal, and # passes every thing else unmodified back. my $string = shift; return Math::BigInt->new($string) if $string =~ /^0[bx]/; # so it must be an octal constant Math::BigInt->from_oct($string); } sub _float_constant { # this takes a floating point constant string and returns it truncated to # integer. For instance, '4.5' => '4', '1.234e2' => '123' etc my $float = shift; # some simple cases first return $float if ($float =~ /^[+-]?[0-9]+$/); # '+123','-1','0' etc return $float if ($float =~ /^[+-]?[0-9]+\.?[eE]\+?[0-9]+$/); # 123e2, 123.e+2 return '0' if ($float =~ /^[+-]?[0]*\.[0-9]+$/); # .2, 0.2, -.1 if ($float =~ /^[+-]?[0-9]+\.[0-9]*$/) { # 1., 1.23, -1.2 etc $float =~ s/\..*//; return $float; } my ($mis, $miv, $mfv, $es, $ev) = Math::BigInt::_split($float); return $float if !defined $mis; # doesn't look like a number to me my $ec = int($$ev); my $sign = $$mis; $sign = '' if $sign eq '+'; if ($$es eq '-') { # ignore fraction part entirely if ($ec >= length($$miv)) { # 123.23E-4 return '0'; } return $sign . substr($$miv, 0, length($$miv) - $ec); # 1234.45E-2 = 12 } # xE+y if ($ec >= length($$mfv)) { $ec -= length($$mfv); return $sign.$$miv.$$mfv if $ec == 0; # 123.45E+2 => 12345 return $sign.$$miv.$$mfv.'E'.$ec; # 123.45e+3 => 12345e1 } $mfv = substr($$mfv, 0, $ec); $sign.$$miv.$mfv; # 123.45e+1 => 1234 } sub unimport { $^H{bigint} = undef; # no longer in effect overload::remove_constant('binary', '', 'float', '', 'integer'); } sub in_effect { my $level = shift || 0; my $hinthash = (caller($level))[10]; $hinthash->{bigint}; } ############################################################################# # the following two routines are for "use bigint qw/hex oct/;": use constant LEXICAL => $] > 5.009004; # Internal function with the same semantics as CORE::hex(). This function is # not used directly, but rather by other front-end functions. sub _hex_core { my $str = shift; # Strip off, clean, and parse as much as we can from the beginning. my $x; if ($str =~ s/ ^ (0?[xX])? ( [0-9a-fA-F]* ( _ [0-9a-fA-F]+ )* ) //x) { my $chrs = $2; $chrs =~ tr/_//d; $chrs = '0' unless CORE::length $chrs; $x = Math::BigInt -> from_hex($chrs); } else { $x = Math::BigInt -> bzero(); } # Warn about trailing garbage. if (CORE::length($str)) { require Carp; Carp::carp(sprintf("Illegal hexadecimal digit '%s' ignored", substr($str, 0, 1))); } return $x; } # Internal function with the same semantics as CORE::oct(). This function is # not used directly, but rather by other front-end functions. sub _oct_core { my $str = shift; $str =~ s/^\s*//; # Hexadecimal input. return _hex_core($str) if $str =~ /^0?[xX]/; my $x; # Binary input. if ($str =~ /^0?[bB]/) { # Strip off, clean, and parse as much as we can from the beginning. if ($str =~ s/ ^ (0?[bB])? ( [01]* ( _ [01]+ )* ) //x) { my $chrs = $2; $chrs =~ tr/_//d; $chrs = '0' unless CORE::length $chrs; $x = Math::BigInt -> from_bin($chrs); } # Warn about trailing garbage. if (CORE::length($str)) { require Carp; Carp::carp(sprintf("Illegal binary digit '%s' ignored", substr($str, 0, 1))); } return $x; } # Octal input. Strip off, clean, and parse as much as we can from the # beginning. if ($str =~ s/ ^ ( [0-7]* ( _ [0-7]+ )* ) //x) { my $chrs = $1; $chrs =~ tr/_//d; $chrs = '0' unless CORE::length $chrs; $x = Math::BigInt -> from_oct($chrs); } # Warn about trailing garbage. CORE::oct() only warns about 8 and 9. if (CORE::length($str)) { my $chr = substr($str, 0, 1); if ($chr eq '8' || $chr eq '9') { require Carp; Carp::carp(sprintf("Illegal octal digit '%s' ignored", $chr)); } } return $x; } { my $proto = LEXICAL ? '_' : ';$'; eval ' sub hex(' . $proto . ') {' . <<'.'; my $str = @_ ? $_[0] : $_; _hex_core($str); } . eval ' sub oct(' . $proto . ') {' . <<'.'; my $str = @_ ? $_[0] : $_; _oct_core($str); } . } ############################################################################# # the following two routines are for Perl 5.9.4 or later and are lexical my ($prev_oct, $prev_hex, $overridden); if (LEXICAL) { eval <<'.' } sub _hex(_) { my $hh = (caller 0)[10]; return $prev_hex ? &$prev_hex($_[0]) : CORE::hex($_[0]) unless $$hh{bigint}||$$hh{bignum}||$$hh{bigrat}; _hex_core($_[0]); } sub _oct(_) { my $hh = (caller 0)[10]; return $prev_oct ? &$prev_oct($_[0]) : CORE::oct($_[0]) unless $$hh{bigint}||$$hh{bignum}||$$hh{bigrat}; _oct_core($_[0]); } . sub _override { return if $overridden; $prev_oct = *CORE::GLOBAL::oct{CODE}; $prev_hex = *CORE::GLOBAL::hex{CODE}; no warnings 'redefine'; *CORE::GLOBAL::oct = \&_oct; *CORE::GLOBAL::hex = \&_hex; $overridden++; } sub import { my $self = shift; $^H{bigint} = 1; # we are in effect # for newer Perls always override hex() and oct() with a lexical version: if (LEXICAL) { _override(); } # some defaults my $lib = ''; my $lib_kind = 'try'; my @import = (':constant'); # drive it w/ constant my @a = @_; my $l = scalar @_; my $j = 0; my ($ver, $trace); # version? trace? my ($a, $p); # accuracy, precision for (my $i = 0; $i < $l; $i++, $j++) { if ($_[$i] =~ /^(l|lib|try|only)$/) { # this causes a different low lib to take care... $lib_kind = $1; $lib_kind = 'lib' if $lib_kind eq 'l'; $lib = $_[$i + 1] || ''; my $s = 2; $s = 1 if @a - $j < 2; # avoid "can not modify non-existent..." splice @a, $j, $s; $j -= $s; $i++; } elsif ($_[$i] =~ /^(a|accuracy)$/) { $a = $_[$i + 1]; my $s = 2; $s = 1 if @a - $j < 2; # avoid "can not modify non-existent..." splice @a, $j, $s; $j -= $s; $i++; } elsif ($_[$i] =~ /^(p|precision)$/) { $p = $_[$i + 1]; my $s = 2; $s = 1 if @a - $j < 2; # avoid "can not modify non-existent..." splice @a, $j, $s; $j -= $s; $i++; } elsif ($_[$i] =~ /^(v|version)$/) { $ver = 1; splice @a, $j, 1; $j--; } elsif ($_[$i] =~ /^(t|trace)$/) { $trace = 1; splice @a, $j, 1; $j--; } elsif ($_[$i] !~ /^(PI|e|bpi|bexp|hex|oct)\z/) { die ("unknown option $_[$i]"); } } my $class; $_lite = 0; # using M::BI::L ? if ($trace) { require Math::BigInt::Trace; $class = 'Math::BigInt::Trace'; } else { # see if we can find Math::BigInt::Lite if (!defined $a && !defined $p) { # rounding won't work to well local @INC = @INC; pop @INC if $INC[-1] eq '.'; if (eval { require Math::BigInt::Lite; 1 }) { @import = (); # :constant in Lite, not MBI Math::BigInt::Lite->import(':constant'); $_lite = 1; # signal okay } } require Math::BigInt if $_lite == 0; # not already loaded? $class = 'Math::BigInt'; # regardless of MBIL or not } push @import, $lib_kind => $lib if $lib ne ''; # Math::BigInt::Trace or plain Math::BigInt $class->import(@import); bigint->accuracy($a) if defined $a; bigint->precision($p) if defined $p; if ($ver) { print "bigint\t\t\t v$VERSION\n"; print "Math::BigInt::Lite\t v$Math::BigInt::Lite::VERSION\n" if $_lite; print "Math::BigInt\t\t v$Math::BigInt::VERSION"; my $config = Math::BigInt->config(); print " lib => $config->{lib} v$config->{lib_version}\n"; exit; } # we take care of floating point constants, since BigFloat isn't available # and BigInt doesn't like them: overload::constant float => sub { Math::BigInt->new(_float_constant(shift)); }; # Take care of octal/hexadecimal constants overload::constant binary => sub { _binary_constant(shift); }; # if another big* was already loaded: my ($package) = caller(); no strict 'refs'; if (!defined *{"${package}::inf"}) { $self->export_to_level(1, $self, @a); # export inf and NaN, e and PI } } sub inf () { Math::BigInt->binf(); } sub NaN () { Math::BigInt->bnan(); } sub PI () { Math::BigInt->new(3); } sub e () { Math::BigInt->new(2); } sub bpi ($) { Math::BigInt->new(3); } sub bexp ($$) { my $x = Math::BigInt->new($_[0]); $x->bexp($_[1]); } 1; __END__ package autodie; use 5.008; use strict; use warnings; use parent qw(Fatal); our $VERSION; # ABSTRACT: Replace functions with ones that succeed or die with lexical scope BEGIN { our $VERSION = '2.29'; # VERSION: Generated by DZP::OurPkg::Version } use constant ERROR_WRONG_FATAL => q{ Incorrect version of Fatal.pm loaded by autodie. The autodie pragma uses an updated version of Fatal to do its heavy lifting. We seem to have loaded Fatal version %s, which is probably the version that came with your version of Perl. However autodie needs version %s, which would have come bundled with autodie. You may be able to solve this problem by adding the following line of code to your main program, before any use of Fatal or autodie. use lib "%s"; }; # We have to check we've got the right version of Fatal before we # try to compile the rest of our code, lest we use a constant # that doesn't exist. BEGIN { # If we have the wrong Fatal, then we've probably loaded the system # one, not our own. Complain, and give a useful hint. ;) if (defined($Fatal::VERSION) and defined($VERSION) and $Fatal::VERSION ne $VERSION) { my $autodie_path = $INC{'autodie.pm'}; $autodie_path =~ s/autodie\.pm//; require Carp; Carp::croak sprintf( ERROR_WRONG_FATAL, $Fatal::VERSION, $VERSION, $autodie_path ); } } # When passing args to Fatal we want to keep the first arg # (our package) in place. Hence the splice. sub import { splice(@_,1,0,Fatal::LEXICAL_TAG); goto &Fatal::import; } sub unimport { splice(@_,1,0,Fatal::LEXICAL_TAG); goto &Fatal::unimport; } 1; __END__ package PerlIO; our $VERSION = '1.10'; # Map layer name to package that defines it our %alias; sub import { my $class = shift; while (@_) { my $layer = shift; if (exists $alias{$layer}) { $layer = $alias{$layer} } else { $layer = "${class}::$layer"; } eval { require $layer =~ s{::}{/}gr . '.pm' }; warn $@ if $@; } } sub F_UTF8 () { 0x8000 } 1; __END__ use strict; use warnings; package perlfaq; $perlfaq::VERSION = '5.021011'; 1; package vars; use 5.006; our $VERSION = '1.04'; use warnings::register; use strict qw(vars subs); sub import { my $callpack = caller; my (undef, @imports) = @_; my ($sym, $ch); foreach (@imports) { if (($ch, $sym) = /^([\$\@\%\*\&])(.+)/) { if ($sym =~ /\W/) { # time for a more-detailed check-up if ($sym =~ /^\w+[[{].*[]}]$/) { require Carp; Carp::croak("Can't declare individual elements of hash or array"); } elsif (warnings::enabled() and length($sym) == 1 and $sym !~ tr/a-zA-Z//) { warnings::warn("No need to declare built-in vars"); } elsif (($^H &= strict::bits('vars'))) { require Carp; Carp::croak("'$_' is not a valid variable name under strict vars"); } } $sym = "${callpack}::$sym" unless $sym =~ /::/; *$sym = ( $ch eq "\$" ? \$$sym : $ch eq "\@" ? \@$sym : $ch eq "\%" ? \%$sym : $ch eq "\*" ? \*$sym : $ch eq "\&" ? \&$sym : do { require Carp; Carp::croak("'$_' is not a valid variable name"); }); } else { require Carp; Carp::croak("'$_' is not a valid variable name"); } } }; 1; __END__ package Exporter; require 5.006; # Be lean. #use strict; #no strict 'refs'; our $Debug = 0; our $ExportLevel = 0; our $Verbose ||= 0; our $VERSION = '5.73'; our (%Cache); sub as_heavy { require Exporter::Heavy; # Unfortunately, this does not work if the caller is aliased as *name = \&foo # Thus the need to create a lot of identical subroutines my $c = (caller(1))[3]; $c =~ s/.*:://; \&{"Exporter::Heavy::heavy_$c"}; } sub export { goto &{as_heavy()}; } sub import { my $pkg = shift; my $callpkg = caller($ExportLevel); if ($pkg eq "Exporter" and @_ and $_[0] eq "import") { *{$callpkg."::import"} = \&import; return; } # We *need* to treat @{"$pkg\::EXPORT_FAIL"} since Carp uses it :-( my $exports = \@{"$pkg\::EXPORT"}; # But, avoid creating things if they don't exist, which saves a couple of # hundred bytes per package processed. my $fail = ${$pkg . '::'}{EXPORT_FAIL} && \@{"$pkg\::EXPORT_FAIL"}; return export $pkg, $callpkg, @_ if $Verbose or $Debug or $fail && @$fail > 1; my $export_cache = ($Cache{$pkg} ||= {}); my $args = @_ or @_ = @$exports; if ($args and not %$export_cache) { s/^&//, $export_cache->{$_} = 1 foreach (@$exports, @{"$pkg\::EXPORT_OK"}); } my $heavy; # Try very hard not to use {} and hence have to enter scope on the foreach # We bomb out of the loop with last as soon as heavy is set. if ($args or $fail) { ($heavy = (/\W/ or $args and not exists $export_cache->{$_} or $fail and @$fail and $_ eq $fail->[0])) and last foreach (@_); } else { ($heavy = /\W/) and last foreach (@_); } return export $pkg, $callpkg, ($args ? @_ : ()) if $heavy; local $SIG{__WARN__} = sub {require Carp; &Carp::carp} if not $SIG{__WARN__}; # shortcut for the common case of no type character *{"$callpkg\::$_"} = \&{"$pkg\::$_"} foreach @_; } # Default methods sub export_fail { my $self = shift; @_; } # Unfortunately, caller(1)[3] "does not work" if the caller is aliased as # *name = \&foo. Thus the need to create a lot of identical subroutines # Otherwise we could have aliased them to export(). sub export_to_level { goto &{as_heavy()}; } sub export_tags { goto &{as_heavy()}; } sub export_ok_tags { goto &{as_heavy()}; } sub require_version { goto &{as_heavy()}; } 1; __END__ package locale; our $VERSION = '1.09'; use Config; $Carp::Internal{ (__PACKAGE__) } = 1; # A separate bit is used for each of the two forms of the pragma, to save # having to look at %^H for the normal case of a plain 'use locale' without an # argument. $locale::hint_bits = 0x4; $locale::partial_hint_bits = 0x10; # If pragma has an argument # The pseudo-category :characters consists of 2 real ones; but it also is # given its own number, -1, because in the complement form it also has the # side effect of "use feature 'unicode_strings'" sub import { shift; # should be 'locale'; not checked $^H{locale} = 0 unless defined $^H{locale}; if (! @_) { # If no parameter, use the plain form that changes all categories $^H |= $locale::hint_bits; } else { my @categories = ( qw(:ctype :collate :messages :numeric :monetary :time) ); for (my $i = 0; $i < @_; $i++) { my $arg = $_[$i]; $complement = $arg =~ s/ : ( ! | not_ ) /:/x; if (! grep { $arg eq $_ } @categories, ":characters") { require Carp; Carp::croak("Unknown parameter '$_[$i]' to 'use locale'"); } if ($complement) { if ($i != 0 || $i < @_ - 1) { require Carp; Carp::croak("Only one argument to 'use locale' allowed" . "if is $complement"); } if ($arg eq ':characters') { push @_, grep { $_ ne ':ctype' && $_ ne ':collate' } @categories; # We add 1 to the category number; This category number # is -1 $^H{locale} |= (1 << 0); } else { push @_, grep { $_ ne $arg } @categories; } next; } elsif ($arg eq ':characters') { push @_, ':ctype', ':collate'; next; } $^H |= $locale::partial_hint_bits; # This form of the pragma overrides the other $^H &= ~$locale::hint_bits; $arg =~ s/^://; eval { require POSIX; import POSIX 'locale_h'; }; # Map our names to the ones defined by POSIX my $LC = "LC_" . uc($arg); my $bit = eval "&POSIX::$LC"; if (defined $bit) { # XXX Should we warn that this category isn't # supported on this platform, or make it # always be the C locale? # Verify our assumption. if (! ($bit >= 0 && $bit < 31)) { require Carp; Carp::croak("Cannot have ':$arg' parameter to 'use locale'" . " on this platform. Use the 'perlbug' utility" . " to report this problem, or send email to" . " 'perlbug\@perl.org'. $LC=$bit"); } # 1 is added so that the pseudo-category :characters, which is # -1, comes out 0. $^H{locale} |= 1 << ($bit + 1); } } } } sub unimport { $^H &= ~($locale::hint_bits|$locale::partial_hint_bits); $^H{locale} = 0; } 1; package sigtrap; use Carp; $VERSION = 1.08; $Verbose ||= 0; sub import { my $pkg = shift; my $handler = \&handler_traceback; my $saw_sig = 0; my $untrapped = 0; local $_; Arg_loop: while (@_) { $_ = shift; if (/^[A-Z][A-Z0-9]*$/) { $saw_sig++; unless ($untrapped and $SIG{$_} and $SIG{$_} ne 'DEFAULT') { print "Installing handler $handler for $_\n" if $Verbose; $SIG{$_} = $handler; } } elsif ($_ eq 'normal-signals') { unshift @_, grep(exists $SIG{$_}, qw(HUP INT PIPE TERM)); } elsif ($_ eq 'error-signals') { unshift @_, grep(exists $SIG{$_}, qw(ABRT BUS EMT FPE ILL QUIT SEGV SYS TRAP)); } elsif ($_ eq 'old-interface-signals') { unshift @_, grep(exists $SIG{$_}, qw(ABRT BUS EMT FPE ILL PIPE QUIT SEGV SYS TERM TRAP)); } elsif ($_ eq 'stack-trace') { $handler = \&handler_traceback; } elsif ($_ eq 'die') { $handler = \&handler_die; } elsif ($_ eq 'handler') { @_ or croak "No argument specified after 'handler'"; $handler = shift; unless (ref $handler or $handler eq 'IGNORE' or $handler eq 'DEFAULT') { require Symbol; $handler = Symbol::qualify($handler, (caller)[0]); } } elsif ($_ eq 'untrapped') { $untrapped = 1; } elsif ($_ eq 'any') { $untrapped = 0; } elsif ($_ =~ /^\d/) { $VERSION >= $_ or croak "sigtrap.pm version $_ required," . " but this is only version $VERSION"; } else { croak "Unrecognized argument $_"; } } unless ($saw_sig) { @_ = qw(old-interface-signals); goto Arg_loop; } } sub handler_die { croak "Caught a SIG$_[0]"; } sub handler_traceback { package DB; # To get subroutine args. $SIG{'ABRT'} = DEFAULT; kill 'ABRT', $$ if $panic++; syswrite(STDERR, 'Caught a SIG', 12); syswrite(STDERR, $_[0], length($_[0])); syswrite(STDERR, ' at ', 4); ($pack,$file,$line) = caller; syswrite(STDERR, $file, length($file)); syswrite(STDERR, ' line ', 6); syswrite(STDERR, $line, length($line)); syswrite(STDERR, "\n", 1); # Now go for broke. for ($i = 1; ($p,$f,$l,$s,$h,$w,$e,$r) = caller($i); $i++) { @a = (); for (@{[@args]}) { s/([\'\\])/\\$1/g; s/([^\0]*)/'$1'/ unless /^(?: -?[\d.]+ | \*[\w:]* )$/x; require 'meta_notation.pm'; $_ = _meta_notation($_) if /[[:^print:]]/a; push(@a, $_); } $w = $w ? '@ = ' : '$ = '; $a = $h ? '(' . join(', ', @a) . ')' : ''; $e =~ s/\n\s*\;\s*\Z// if $e; $e =~ s/[\\\']/\\$1/g if $e; if ($r) { $s = "require '$e'"; } elsif (defined $r) { $s = "eval '$e'"; } elsif ($s eq '(eval)') { $s = "eval {...}"; } $f = "file '$f'" unless $f eq '-e'; $mess = "$w$s$a called from $f line $l\n"; syswrite(STDERR, $mess, length($mess)); } kill 'ABRT', $$; } 1; __END__ package DirHandle; our $VERSION = '1.05'; require 5.000; use Carp; use Symbol; sub new { @_ >= 1 && @_ <= 2 or croak 'usage: DirHandle->new( [DIRNAME] )'; my $class = shift; my $dh = gensym; if (@_) { DirHandle::open($dh, $_[0]) or return undef; } bless $dh, $class; } sub DESTROY { my ($dh) = @_; # Don't warn about already being closed as it may have been closed # correctly, or maybe never opened at all. local($., $@, $!, $^E, $?); no warnings 'io'; closedir($dh); } sub open { @_ == 2 or croak 'usage: $dh->open(DIRNAME)'; my ($dh, $dirname) = @_; opendir($dh, $dirname); } sub close { @_ == 1 or croak 'usage: $dh->close()'; my ($dh) = @_; closedir($dh); } sub read { @_ == 1 or croak 'usage: $dh->read()'; my ($dh) = @_; readdir($dh); } sub rewind { @_ == 1 or croak 'usage: $dh->rewind()'; my ($dh) = @_; rewinddir($dh); } 1; use strict; use warnings; # A tiny private library routine which is a helper to several Perl core # modules, to allow a paradigm to be implemented in a single place. The name, # contents, or even the existence of this file may be changed at any time and # are NOT to be used by anything outside the Perl core. sub _meta_notation ($) { # Returns a copy of the input string with the nonprintable characters # below 0x100 changed into printables. Any ASCII printables or above 0xFF # are unchanged. (XXX Probably above-Latin1 characters should be # converted to \X{...}) # # \0 .. \x1F (which are "\c@" .. "\c_") are changed into ^@, ^A, ^B, ... # ^Z, ^[, ^\, ^], ^^, ^_ # \c? is changed into ^?. # # The above accounts for all the ASCII-range nonprintables. # # On ASCII platforms, the upper-Latin1-range characters are converted to # Meta notation, so that \xC1 becomes 'M-A', \xE2 becomes 'M-b', etc. # This is how it always has worked, so is continued that way for backwards # compatibility. The range \x80 .. \x9F becomes M-^@ .. M-^A, M-^B, ... # M-^Z, M-^[, M-^\, M-^], M-^, M-^_ # # On EBCDIC platforms, the upper-Latin1-range characters are converted # into '\x{...}' Meta notation doesn't make sense on EBCDIC platforms # because the ASCII-range printables are a mixture of upper bit set or # not. [A-Za-Z0-9] all have the upper bit set. The underscore likely # doesn't; and other punctuation may or may not. There's no simple # pattern. my $string = shift; $string =~ s/([\0-\037])/ sprintf("^%c",utf8::unicode_to_native(ord($1)^64))/xeg; $string =~ s/\c?/^?/g; if (ord("A") == 65) { $string =~ s/([\200-\237])/sprintf("M-^%c",(ord($1)&0177)^64)/eg; $string =~ s/([\240-\377])/sprintf("M-%c" ,ord($1)&0177)/eg; } else { no warnings 'experimental::regex_sets'; # Leave alone things above \xff $string =~ s/( (?[ [\x00-\xFF] & [:^print:]])) / sprintf("\\x{%X}", ord($1))/xaeg; } return $string; } 1 package autodie::Util; use strict; use warnings; use Exporter 5.57 qw(import); use autodie::Scope::GuardStack; our @EXPORT_OK = qw( fill_protos install_subs make_core_trampoline on_end_of_compile_scope ); our $VERSION = '2.29'; # VERSION: Generated by DZP::OurPkg:Version # ABSTRACT: Internal Utility subroutines for autodie and Fatal # docs says we should pick __PACKAGE__ / my $H_STACK_KEY = __PACKAGE__ . '/stack'; sub on_end_of_compile_scope { my ($hook) = @_; # Dark magic to have autodie work under 5.8 # Copied from namespace::clean, that copied it from # autobox, that found it on an ancient scroll written # in blood. # This magic bit causes %^H to be lexically scoped. $^H |= 0x020000; my $stack = $^H{$H_STACK_KEY}; if (not defined($stack)) { $stack = autodie::Scope::GuardStack->new; $^H{$H_STACK_KEY} = $stack; } $stack->push_hook($hook); return; } # This code is based on code from the original Fatal. The "XXXX" # remark is from the original code and its meaning is (sadly) unknown. sub fill_protos { my ($proto) = @_; my ($n, $isref, @out, @out1, $seen_semi) = -1; if ($proto =~ m{^\s* (?: [;] \s*)? \@}x) { # prototype is entirely slurply - special case that does not # require any handling. return ([0, '@_']); } while ($proto =~ /\S/) { $n++; push(@out1,[$n,@out]) if $seen_semi; push(@out, $1 . "{\$_[$n]}"), next if $proto =~ s/^\s*\\([\@%\$\&])//; push(@out, "\$_[$n]"), next if $proto =~ s/^\s*([_*\$&])//; push(@out, "\@_[$n..\$#_]"), last if $proto =~ s/^\s*(;\s*)?\@//; $seen_semi = 1, $n--, next if $proto =~ s/^\s*;//; # XXXX ???? die "Internal error: Unknown prototype letters: \"$proto\""; } push(@out1,[$n+1,@out]); return @out1; } sub make_core_trampoline { my ($call, $pkg, $proto_str) = @_; my $trampoline_code = 'sub {'; my $trampoline_sub; my @protos = fill_protos($proto_str); foreach my $proto (@protos) { local $" = ", "; # So @args is formatted correctly. my ($count, @args) = @$proto; if (@args && $args[-1] =~ m/[@#]_/) { $trampoline_code .= qq/ if (\@_ >= $count) { return $call(@args); } /; } else { $trampoline_code .= qq< if (\@_ == $count) { return $call(@args); } >; } } $trampoline_code .= qq< require Carp; Carp::croak("Internal error in Fatal/autodie. Leak-guard failure"); } >; my $E; { local $@; $trampoline_sub = eval "package $pkg;\n $trampoline_code"; ## no critic $E = $@; } die "Internal error in Fatal/autodie: Leak-guard installation failure: $E" if $E; return $trampoline_sub; } # The code here is originally lifted from namespace::clean, # by Robert "phaylon" Sedlacek. # # It's been redesigned after feedback from ikegami on perlmonks. # See http://perlmonks.org/?node_id=693338 . Ikegami rocks. # # Given a package, and hash of (subname => subref) pairs, # we install the given subroutines into the package. If # a subref is undef, the subroutine is removed. Otherwise # it replaces any existing subs which were already there. sub install_subs { my ($target_pkg, $subs_to_reinstate) = @_; my $pkg_sym = "${target_pkg}::"; # It does not hurt to do this in a predictable order, and might help debugging. foreach my $sub_name (sort keys(%{$subs_to_reinstate})) { # We will repeatedly mess with stuff that strict "refs" does # not like. So lets just disable it once for this entire # scope. no strict qw(refs); ## no critic my $sub_ref = $subs_to_reinstate->{$sub_name}; my $full_path = ${pkg_sym}.${sub_name}; my $oldglob = *$full_path; # Nuke the old glob. delete($pkg_sym->{$sub_name}); # For some reason this local *alias = *$full_path triggers an # "only used once" warning. Not entirely sure why, but at # least it is easy to silence. no warnings qw(once); local *alias = *$full_path; use warnings qw(once); # Copy innocent bystanders back. Note that we lose # formats; it seems that Perl versions up to 5.10.0 # have a bug which causes copying formats to end up in # the scalar slot. Thanks to Ben Morrow for spotting this. foreach my $slot (qw( SCALAR ARRAY HASH IO ) ) { next unless defined(*$oldglob{$slot}); *alias = *$oldglob{$slot}; } if ($sub_ref) { *$full_path = $sub_ref; } } return; } 1; __END__ package autodie::skip; use strict; use warnings; our $VERSION = '2.29'; # VERSION # This package exists purely so people can inherit from it, # which isn't at all how roles are supposed to work, but it's # how people will use them anyway. if ($] < 5.010) { # Older Perls don't have a native ->DOES. Let's provide a cheap # imitation here. *DOES = sub { return shift->isa(@_); }; } 1; __END__ package autodie::hints; use strict; use warnings; use constant PERL58 => ( $] < 5.009 ); our $VERSION = '2.29001'; # ABSTRACT: Provide hints about user subroutines to autodie # TODO: implement regular expression hints use constant UNDEF_ONLY => sub { not defined $_[0] }; use constant EMPTY_OR_UNDEF => sub { ! @_ or @_==1 && !defined $_[0] }; use constant EMPTY_ONLY => sub { @_ == 0 }; use constant EMPTY_OR_FALSE => sub { ! @_ or @_==1 && !$_[0] }; use constant SINGLE_TRUE => sub { @_ == 1 and not $_[0] }; use constant DEFAULT_HINTS => { scalar => UNDEF_ONLY, list => EMPTY_OR_UNDEF, }; use constant HINTS_PROVIDER => 'autodie::hints::provider'; our $DEBUG = 0; # Only ( undef ) is a strange but possible situation for very # badly written code. It's not supported yet. my %Hints = ( 'File::Copy::copy' => { scalar => SINGLE_TRUE, list => SINGLE_TRUE }, 'File::Copy::move' => { scalar => SINGLE_TRUE, list => SINGLE_TRUE }, 'File::Copy::cp' => { scalar => SINGLE_TRUE, list => SINGLE_TRUE }, 'File::Copy::mv' => { scalar => SINGLE_TRUE, list => SINGLE_TRUE }, ); # Start by using Sub::Identify if it exists on this system. eval { require Sub::Identify; Sub::Identify->import('get_code_info'); }; # If it doesn't exist, we'll define our own. This code is directly # taken from Rafael Garcia's Sub::Identify 0.04, used under the same # license as Perl itself. if ($@) { require B; no warnings 'once'; *get_code_info = sub ($) { my ($coderef) = @_; ref $coderef or return; my $cv = B::svref_2object($coderef); $cv->isa('B::CV') or return; # bail out if GV is undefined $cv->GV->isa('B::SPECIAL') and return; return ($cv->GV->STASH->NAME, $cv->GV->NAME); }; } sub sub_fullname { return join( '::', get_code_info( $_[1] ) ); } my %Hints_loaded = (); sub load_hints { my ($class, $sub) = @_; my ($package) = ( $sub =~ /(.*)::/ ); if (not defined $package) { require Carp; Carp::croak( "Internal error in autodie::hints::load_hints - no package found. "); } # Do nothing if we've already tried to load hints for # this package. return if $Hints_loaded{$package}++; my $hints_available = 0; { no strict 'refs'; ## no critic if ($package->can('DOES') and $package->DOES(HINTS_PROVIDER) ) { $hints_available = 1; } elsif ( PERL58 and $package->isa(HINTS_PROVIDER) ) { $hints_available = 1; } elsif ( ${"${package}::DOES"}{HINTS_PROVIDER.""} ) { $hints_available = 1; } } return if not $hints_available; my %package_hints = %{ $package->AUTODIE_HINTS }; foreach my $sub (keys %package_hints) { my $hint = $package_hints{$sub}; # Ensure we have a package name. $sub = "${package}::$sub" if $sub !~ /::/; # TODO - Currently we don't check for conflicts, should we? $Hints{$sub} = $hint; $class->normalise_hints(\%Hints, $sub); } return; } sub normalise_hints { my ($class, $hints, $sub) = @_; if ( exists $hints->{$sub}->{fail} ) { if ( exists $hints->{$sub}->{scalar} or exists $hints->{$sub}->{list} ) { # TODO: Turn into a proper diagnostic. require Carp; local $Carp::CarpLevel = 1; Carp::croak("fail hints cannot be provided with either scalar or list hints for $sub"); } # Set our scalar and list hints. $hints->{$sub}->{scalar} = $hints->{$sub}->{list} = delete $hints->{$sub}->{fail}; return; } # Check to make sure all our hints exist. foreach my $hint (qw(scalar list)) { if ( not exists $hints->{$sub}->{$hint} ) { # TODO: Turn into a proper diagnostic. require Carp; local $Carp::CarpLevel = 1; Carp::croak("$hint hint missing for $sub"); } } return; } sub get_hints_for { my ($class, $sub) = @_; my $subname = $class->sub_fullname( $sub ); # If we have hints loaded for a sub, then return them. if ( exists $Hints{ $subname } ) { return $Hints{ $subname }; } # If not, we try to load them... $class->load_hints( $subname ); # ...and try again! if ( exists $Hints{ $subname } ) { return $Hints{ $subname }; } # It's the caller's responsibility to use defaults if desired. # This allows on autodie to insist on hints if needed. return; } sub set_hints_for { my ($class, $sub, $hints) = @_; if (ref $sub) { $sub = $class->sub_fullname( $sub ); require Carp; $sub or Carp::croak("Attempts to set_hints_for unidentifiable subroutine"); } if ($DEBUG) { warn "autodie::hints: Setting $sub to hints: $hints\n"; } $Hints{ $sub } = $hints; $class->normalise_hints(\%Hints, $sub); return; } 1; __END__ package autodie::exception; use 5.008; use strict; use warnings; use Carp qw(croak); our $VERSION = '2.29002'; # ABSTRACT: Exceptions from autodying functions. our $DEBUG = 0; use overload q{""} => "stringify", # Overload smart-match only if we're using 5.10 or up ($] >= 5.010 ? ('~~' => "matches") : ()), fallback => 1 ; my $PACKAGE = __PACKAGE__; # Useful to have a scalar for hash keys. sub args { return $_[0]->{$PACKAGE}{args}; } sub function { return $_[0]->{$PACKAGE}{function}; } sub file { return $_[0]->{$PACKAGE}{file}; } sub package { return $_[0]->{$PACKAGE}{package}; } sub caller { return $_[0]->{$PACKAGE}{caller}; } sub line { return $_[0]->{$PACKAGE}{line}; } # TODO: The comments above say this can be undefined. Is that actually # the case? (With 'system', perhaps?) sub context { return $_[0]->{$PACKAGE}{context} } sub return { return $_[0]->{$PACKAGE}{return} } # TODO: Make errno part of a role. It doesn't make sense for # everything. sub errno { return $_[0]->{$PACKAGE}{errno}; } sub eval_error { return $_[0]->{$PACKAGE}{eval_error}; } { my (%cache); sub matches { my ($this, $that) = @_; # TODO - Handle references croak "UNIMPLEMENTED" if ref $that; my $sub = $this->function; if ($DEBUG) { my $sub2 = $this->function; warn "Smart-matching $that against $sub / $sub2\n"; } # Direct subname match. return 1 if $that eq $sub; return 1 if $that !~ /:/ and "CORE::$that" eq $sub; return 0 if $that !~ /^:/; # Cached match / check tags. require Fatal; if (exists $cache{$sub}{$that}) { return $cache{$sub}{$that}; } # This rather awful looking line checks to see if our sub is in the # list of expanded tags, caches it, and returns the result. return $cache{$sub}{$that} = grep { $_ eq $sub } @{ $this->_expand_tag($that) }; } } # This exists primarily so that child classes can override or # augment it if they wish. sub _expand_tag { my ($this, @args) = @_; return Fatal->_expand_tag(@args); } # The table below records customer formatters. # TODO - Should this be a package var instead? # TODO - Should these be in a completely different file, or # perhaps loaded on demand? Most formatters will never # get used in most programs. my %formatter_of = ( 'CORE::close' => \&_format_close, 'CORE::open' => \&_format_open, 'CORE::dbmopen' => \&_format_dbmopen, 'CORE::flock' => \&_format_flock, 'CORE::read' => \&_format_readwrite, 'CORE::sysread' => \&_format_readwrite, 'CORE::syswrite' => \&_format_readwrite, 'CORE::chmod' => \&_format_chmod, 'CORE::mkdir' => \&_format_mkdir, ); sub _beautify_arguments { shift @_; # Walk through all our arguments, and... # # * Replace undef with the word 'undef' # * Replace globs with the string '$fh' # * Quote all other args. foreach my $arg (@_) { if (not defined($arg)) { $arg = 'undef' } elsif (ref($arg) eq "GLOB") { $arg = '$fh' } else { $arg = qq{'$arg'} } } return @_; } sub _trim_package_name { # Info: The following is done since 05/2008 (which is before v1.10) # TODO: This is probably a good idea for CORE, is it # a good idea for other subs? # Trim package name off dying sub for error messages (my $name = $_[1]) =~ s/.*:://; return $name; } # Returns the parameter formatted as octal number sub _octalize_number { my $number = $_[1]; # Only reformat if it looks like a whole number if ($number =~ /^\d+$/) { $number = sprintf("%#04lo", $number); } return $number; } # TODO: Our tests only check LOCK_EX | LOCK_NB is properly # formatted. Try other combinations and ensure they work # correctly. sub _format_flock { my ($this) = @_; require Fcntl; my $filehandle = $this->args->[0]; my $raw_mode = $this->args->[1]; my $mode_type; my $lock_unlock; if ($raw_mode & Fcntl::LOCK_EX() ) { $lock_unlock = "lock"; $mode_type = "for exclusive access"; } elsif ($raw_mode & Fcntl::LOCK_SH() ) { $lock_unlock = "lock"; $mode_type = "for shared access"; } elsif ($raw_mode & Fcntl::LOCK_UN() ) { $lock_unlock = "unlock"; $mode_type = ""; } else { # I've got no idea what they're trying to do. $lock_unlock = "lock"; $mode_type = "with mode $raw_mode"; } my $cooked_filehandle; if ($filehandle and not ref $filehandle) { # A package filehandle with a name! $cooked_filehandle = " $filehandle"; } else { # Otherwise we have a scalar filehandle. $cooked_filehandle = ''; } local $! = $this->errno; return "Can't $lock_unlock filehandle$cooked_filehandle $mode_type: $!"; } # Default formatter for CORE::chmod sub _format_chmod { my ($this) = @_; my @args = @{$this->args}; my $mode = shift @args; local $! = $this->errno; $mode = $this->_octalize_number($mode); @args = $this->_beautify_arguments(@args); return "Can't chmod($mode, ". join(q{, }, @args) ."): $!"; } # Default formatter for CORE::mkdir sub _format_mkdir { my ($this) = @_; my @args = @{$this->args}; # If no mask is specified use default formatter if (@args < 2) { return $this->format_default; } my $file = $args[0]; my $mask = $args[1]; local $! = $this->errno; $mask = $this->_octalize_number($mask); return "Can't mkdir('$file', $mask): '$!'"; } # Default formatter for CORE::dbmopen sub _format_dbmopen { my ($this) = @_; my @args = @{$this->args}; # TODO: Presently, $args flattens out the (usually empty) hash # which is passed as the first argument to dbmopen. This is # a bug in our args handling code (taking a reference to it would # be better), but for the moment we'll just examine the end of # our arguments list for message formatting. my $mode = $args[-1]; my $file = $args[-2]; $mode = $this->_octalize_number($mode); local $! = $this->errno; return "Can't dbmopen(%hash, '$file', $mode): '$!'"; } # Default formatter for CORE::close sub _format_close { my ($this) = @_; my $close_arg = $this->args->[0]; local $! = $this->errno; # If we've got an old-style filehandle, mention it. if ($close_arg and not ref $close_arg) { return "Can't close filehandle '$close_arg': '$!'"; } # TODO - This will probably produce an ugly error. Test and fix. return "Can't close($close_arg) filehandle: '$!'"; } # Default formatter for CORE::read, CORE::sysread and CORE::syswrite # # Similar to default formatter with the buffer filtered out as it # may contain binary data. sub _format_readwrite { my ($this) = @_; my $call = $this->_trim_package_name($this->function); local $! = $this->errno; # These subs receive the following arguments (in order): # # * FILEHANDLE # * SCALAR (buffer, we do not want to write this) # * LENGTH (optional for syswrite) # * OFFSET (optional for all) my (@args) = @{$this->args}; my $arg_name = $args[1]; if (defined($arg_name)) { if (ref($arg_name)) { my $name = blessed($arg_name) || ref($arg_name); $arg_name = "<${name}>"; } else { $arg_name = ''; } } else { $arg_name = ''; } $args[1] = $arg_name; return "Can't $call(" . join(q{, }, @args) . "): $!"; } # Default formatter for CORE::open use constant _FORMAT_OPEN => "Can't open '%s' for %s: '%s'"; sub _format_open_with_mode { my ($this, $mode, $file, $error) = @_; my $wordy_mode; if ($mode eq '<') { $wordy_mode = 'reading'; } elsif ($mode eq '>') { $wordy_mode = 'writing'; } elsif ($mode eq '>>') { $wordy_mode = 'appending'; } $file = '' if not defined $file; return sprintf _FORMAT_OPEN, $file, $wordy_mode, $error if $wordy_mode; Carp::confess("Internal autodie::exception error: Don't know how to format mode '$mode'."); } sub _format_open { my ($this) = @_; my @open_args = @{$this->args}; # Use the default formatter for single-arg and many-arg open if (@open_args <= 1 or @open_args >= 4) { return $this->format_default; } # For two arg open, we have to extract the mode if (@open_args == 2) { my ($fh, $file) = @open_args; if (ref($fh) eq "GLOB") { $fh = '$fh'; } my ($mode) = $file =~ m{ ^\s* # Spaces before mode ( (?> # Non-backtracking subexp. < # Reading |>>? # Writing/appending ) ) [^&] # Not an ampersand (which means a dup) }x; if (not $mode) { # Maybe it's a 2-arg open without any mode at all? # Detect the most simple case for this, where our # file consists only of word characters. if ( $file =~ m{^\s*\w+\s*$} ) { $mode = '<' } else { # Otherwise, we've got no idea what's going on. # Use the default. return $this->format_default; } } # Localising $! means perl makes it a pretty error for us. local $! = $this->errno; return $this->_format_open_with_mode($mode, $file, $!); } # Here we must be using three arg open. my $file = $open_args[2]; local $! = $this->errno; my $mode = $open_args[1]; local $@; my $msg = eval { $this->_format_open_with_mode($mode, $file, $!); }; return $msg if $msg; # Default message (for pipes and odd things) return "Can't open '$file' with mode '$open_args[1]': '$!'"; } sub register { my ($class, $symbol, $handler) = @_; croak "Incorrect call to autodie::register" if @_ != 3; $formatter_of{$symbol} = $handler; } # Simply produces the file and line number; intended to be added # to the end of error messages. sub add_file_and_line { my ($this) = @_; return sprintf(" at %s line %d\n", $this->file, $this->line); } sub stringify { my ($this) = @_; my $call = $this->function; my $msg; if ($DEBUG) { my $dying_pkg = $this->package; my $sub = $this->function; my $caller = $this->caller; warn "Stringifing exception for $dying_pkg :: $sub / $caller / $call\n"; } # TODO - This isn't using inheritance. Should it? if ( my $sub = $formatter_of{$call} ) { $msg = $sub->($this) . $this->add_file_and_line; } else { $msg = $this->format_default . $this->add_file_and_line; } $msg .= $this->{$PACKAGE}{_stack_trace} if $Carp::Verbose; return $msg; } # TODO: This produces ugly errors. Is there any way we can # dig around to find the actual variable names? I know perl 5.10 # does some dark and terrible magicks to find them for undef warnings. sub format_default { my ($this) = @_; my $call = $this->_trim_package_name($this->function); local $! = $this->errno; my @args = @{ $this->args() }; @args = $this->_beautify_arguments(@args); # Format our beautiful error. return "Can't $call(". join(q{, }, @args) . "): $!" ; # TODO - Handle user-defined errors from hash. # TODO - Handle default error messages. } sub new { my ($class, @args) = @_; my $this = {}; bless($this,$class); # I'd love to use EVERY here, but it causes our code to die # because it wants to stringify our objects before they're # initialised, causing everything to explode. $this->_init(@args); return $this; } sub _init { my ($this, %args) = @_; # Capturing errno here is not necessarily reliable. my $original_errno = $!; our $init_called = 1; my $class = ref $this; # We're going to walk up our call stack, looking for the # first thing that doesn't look like our exception # code, autodie/Fatal, or some whacky eval. my ($package, $file, $line, $sub); my $depth = 0; while (1) { $depth++; ($package, $file, $line, $sub) = CORE::caller($depth); # Skip up the call stack until we find something outside # of the Fatal/autodie/eval space. next if $package->isa('Fatal'); next if $package->isa($class); next if $package->isa(__PACKAGE__); # Anything with the 'autodie::skip' role wants us to skip it. # https://github.com/pjf/autodie/issues/15 next if ($package->can('DOES') and $package->DOES('autodie::skip')); next if $file =~ /^\(eval\s\d+\)$/; last; } # We now have everything correct, *except* for our subroutine # name. If it's __ANON__ or (eval), then we need to keep on # digging deeper into our stack to find the real name. However we # don't update our other information, since that will be correct # for our current exception. my $first_guess_subroutine = $sub; while (defined $sub and $sub =~ /^\(eval\)$|::__ANON__$/) { $depth++; $sub = (CORE::caller($depth))[3]; } # If we end up falling out the bottom of our stack, then our # __ANON__ guess is the best we can get. This includes situations # where we were called from the top level of a program. if (not defined $sub) { $sub = $first_guess_subroutine; } $this->{$PACKAGE}{package} = $package; $this->{$PACKAGE}{file} = $file; $this->{$PACKAGE}{line} = $line; $this->{$PACKAGE}{caller} = $sub; # Tranks to %Carp::CarpInternal all Fatal, autodie and # autodie::exception stack frames are filtered already, but our # nameless wrapper is still present, so strip that. my $trace = Carp::longmess(); $trace =~ s/^\s*at \(eval[^\n]+\n//; # And if we see an __ANON__, then we'll replace that with the actual # name of our autodying function. my $short_func = $args{function}; $short_func =~ s/^CORE:://; $trace =~ s/(\s*[\w:]+)__ANON__/$1$short_func/; # And now we just fill in all our attributes. $this->{$PACKAGE}{_stack_trace} = $trace; $this->{$PACKAGE}{errno} = $args{errno} || 0; $this->{$PACKAGE}{context} = $args{context}; $this->{$PACKAGE}{return} = $args{return}; $this->{$PACKAGE}{eval_error} = $args{eval_error}; $this->{$PACKAGE}{args} = $args{args} || []; $this->{$PACKAGE}{function}= $args{function} or croak("$class->new() called without function arg"); return $this; } 1; __END__ package autodie::exception::system; use 5.008; use strict; use warnings; use parent 'autodie::exception'; use Carp qw(croak); our $VERSION = '2.29'; # VERSION: Generated by DZP::OurPkg:Version # ABSTRACT: Exceptions from autodying system(). my $PACKAGE = __PACKAGE__; sub _init { my ($this, %args) = @_; $this->{$PACKAGE}{message} = $args{message} || croak "'message' arg not supplied to autodie::exception::system->new"; return $this->SUPER::_init(%args); } sub stringify { my ($this) = @_; return $this->{$PACKAGE}{message} . $this->add_file_and_line; } 1; __END__ package autodie::Scope::Guard; use strict; use warnings; # ABSTRACT: Wrapper class for calling subs at end of scope our $VERSION = '2.29'; # VERSION # This code schedules the cleanup of subroutines at the end of # scope. It's directly inspired by chocolateboy's excellent # Scope::Guard module. sub new { my ($class, $handler) = @_; return bless($handler, $class); } sub DESTROY { my ($self) = @_; $self->(); } 1; __END__ package autodie::Scope::GuardStack; use strict; use warnings; use autodie::Scope::Guard; # ABSTRACT: Hook stack for managing scopes via %^H our $VERSION = '2.29'; # VERSION my $H_KEY_STEM = __PACKAGE__ . '/guard'; my $COUNTER = 0; # This code schedules the cleanup of subroutines at the end of # scope. It's directly inspired by chocolateboy's excellent # Scope::Guard module. sub new { my ($class) = @_; return bless([], $class); } sub push_hook { my ($self, $hook) = @_; my $h_key = $H_KEY_STEM . ($COUNTER++); my $size = @{$self}; $^H{$h_key} = autodie::Scope::Guard->new(sub { # Pop the stack until we reach the right size # - this may seem weird, but it is to avoid relying # on "destruction order" of keys in %^H. # # Example: # { # use autodie; # hook 1 # no autodie; # hook 2 # use autodie; # hook 3 # } # # Here we want call hook 3, then hook 2 and finally hook 1. # Any other order could have undesired consequences. # # Suppose hook 2 is destroyed first, it will pop hook 3 and # then hook 2. hook 3 will then be destroyed, but do nothing # since its "frame" was already popped and finally hook 1 # will be popped and take its own frame with it. # # We need to check that $self still exists since things can get weird # during global destruction. $self->_pop_hook while $self && @{$self} > $size; }); push(@{$self}, [$hook, $h_key]); return; } sub _pop_hook { my ($self) = @_; my ($hook, $key) = @{ pop(@{$self}) }; my $ref = delete($^H{$key}); $hook->(); return; } sub DESTROY { my ($self) = @_; # To be honest, I suspect @{$self} will always be empty here due # to the subs in %^H having references to the stack (which would # keep the stack alive until those have been destroyed). Anyhow, # it never hurt to be careful. $self->_pop_hook while @{$self}; return; } 1; __END__ package Tie::Array; use 5.006_001; use strict; use Carp; our $VERSION = '1.07'; # Pod documentation after __END__ below. sub DESTROY { } sub EXTEND { } sub UNSHIFT { scalar shift->SPLICE(0,0,@_) } sub SHIFT { shift->SPLICE(0,1) } sub CLEAR { shift->STORESIZE(0) } sub PUSH { my $obj = shift; my $i = $obj->FETCHSIZE; $obj->STORE($i++, shift) while (@_); } sub POP { my $obj = shift; my $newsize = $obj->FETCHSIZE - 1; my $val; if ($newsize >= 0) { $val = $obj->FETCH($newsize); $obj->STORESIZE($newsize); } $val; } sub SPLICE { my $obj = shift; my $sz = $obj->FETCHSIZE; my $off = (@_) ? shift : 0; $off += $sz if ($off < 0); my $len = (@_) ? shift : $sz - $off; $len += $sz - $off if $len < 0; my @result; for (my $i = 0; $i < $len; $i++) { push(@result,$obj->FETCH($off+$i)); } $off = $sz if $off > $sz; $len -= $off + $len - $sz if $off + $len > $sz; if (@_ > $len) { # Move items up to make room my $d = @_ - $len; my $e = $off+$len; $obj->EXTEND($sz+$d); for (my $i=$sz-1; $i >= $e; $i--) { my $val = $obj->FETCH($i); $obj->STORE($i+$d,$val); } } elsif (@_ < $len) { # Move items down to close the gap my $d = $len - @_; my $e = $off+$len; for (my $i=$off+$len; $i < $sz; $i++) { my $val = $obj->FETCH($i); $obj->STORE($i-$d,$val); } $obj->STORESIZE($sz-$d); } for (my $i=0; $i < @_; $i++) { $obj->STORE($off+$i,$_[$i]); } return wantarray ? @result : pop @result; } sub EXISTS { my $pkg = ref $_[0]; croak "$pkg doesn't define an EXISTS method"; } sub DELETE { my $pkg = ref $_[0]; croak "$pkg doesn't define a DELETE method"; } package Tie::StdArray; our @ISA = 'Tie::Array'; sub TIEARRAY { bless [], $_[0] } sub FETCHSIZE { scalar @{$_[0]} } sub STORESIZE { $#{$_[0]} = $_[1]-1 } sub STORE { $_[0]->[$_[1]] = $_[2] } sub FETCH { $_[0]->[$_[1]] } sub CLEAR { @{$_[0]} = () } sub POP { pop(@{$_[0]}) } sub PUSH { my $o = shift; push(@$o,@_) } sub SHIFT { shift(@{$_[0]}) } sub UNSHIFT { my $o = shift; unshift(@$o,@_) } sub EXISTS { exists $_[0]->[$_[1]] } sub DELETE { delete $_[0]->[$_[1]] } sub SPLICE { my $ob = shift; my $sz = $ob->FETCHSIZE; my $off = @_ ? shift : 0; $off += $sz if $off < 0; my $len = @_ ? shift : $sz-$off; return splice(@$ob,$off,$len,@_); } 1; __END__ package Tie::SubstrHash; our $VERSION = '1.00'; use Carp; sub TIEHASH { my $pack = shift; my ($klen, $vlen, $tsize) = @_; my $rlen = 1 + $klen + $vlen; $tsize = [$tsize, findgteprime($tsize * 1.1)]; # Allow 10% empty. local $self = bless ["\0", $klen, $vlen, $tsize, $rlen, 0, -1]; $$self[0] x= $rlen * $tsize->[1]; $self; } sub CLEAR { local($self) = @_; $$self[0] = "\0" x ($$self[4] * $$self[3]->[1]); $$self[5] = 0; $$self[6] = -1; } sub FETCH { local($self,$key) = @_; local($klen, $vlen, $tsize, $rlen) = @$self[1..4]; &hashkey; for (;;) { $offset = $hash * $rlen; $record = substr($$self[0], $offset, $rlen); if (ord($record) == 0) { return undef; } elsif (ord($record) == 1) { } elsif (substr($record, 1, $klen) eq $key) { return substr($record, 1+$klen, $vlen); } &rehash; } } sub STORE { local($self,$key,$val) = @_; local($klen, $vlen, $tsize, $rlen) = @$self[1..4]; croak("Table is full ($tsize->[0] elements)") if $$self[5] > $tsize->[0]; croak(qq/Value "$val" is not $vlen characters long/) if length($val) != $vlen; my $writeoffset; &hashkey; for (;;) { $offset = $hash * $rlen; $record = substr($$self[0], $offset, $rlen); if (ord($record) == 0) { $record = "\2". $key . $val; die "panic" unless length($record) == $rlen; $writeoffset = $offset unless defined $writeoffset; substr($$self[0], $writeoffset, $rlen) = $record; ++$$self[5]; return; } elsif (ord($record) == 1) { $writeoffset = $offset unless defined $writeoffset; } elsif (substr($record, 1, $klen) eq $key) { $record = "\2". $key . $val; die "panic" unless length($record) == $rlen; substr($$self[0], $offset, $rlen) = $record; return; } &rehash; } } sub DELETE { local($self,$key) = @_; local($klen, $vlen, $tsize, $rlen) = @$self[1..4]; &hashkey; for (;;) { $offset = $hash * $rlen; $record = substr($$self[0], $offset, $rlen); if (ord($record) == 0) { return undef; } elsif (ord($record) == 1) { } elsif (substr($record, 1, $klen) eq $key) { substr($$self[0], $offset, 1) = "\1"; return substr($record, 1+$klen, $vlen); --$$self[5]; } &rehash; } } sub FIRSTKEY { local($self) = @_; $$self[6] = -1; &NEXTKEY; } sub NEXTKEY { local($self) = @_; local($klen, $vlen, $tsize, $rlen, $entries, $iterix) = @$self[1..6]; for (++$iterix; $iterix < $tsize->[1]; ++$iterix) { next unless substr($$self[0], $iterix * $rlen, 1) eq "\2"; $$self[6] = $iterix; return substr($$self[0], $iterix * $rlen + 1, $klen); } $$self[6] = -1; undef; } sub EXISTS { croak "Tie::SubstrHash does not support exists()"; } sub hashkey { croak(qq/Key "$key" is not $klen characters long/) if length($key) != $klen; $hash = 2; for (unpack('C*', $key)) { $hash = $hash * 33 + $_; &_hashwrap if $hash >= 1e13; } &_hashwrap if $hash >= $tsize->[1]; $hash = 1 unless $hash; $hashbase = $hash; } sub _hashwrap { $hash -= int($hash / $tsize->[1]) * $tsize->[1]; } sub rehash { $hash += $hashbase; $hash -= $tsize->[1] if $hash >= $tsize->[1]; } # using POSIX::ceil() would be too heavy, and not all platforms have it. sub ceil { my $num = shift; $num = int($num + 1) unless $num == int $num; return $num; } # See: # # http://www-groups.dcs.st-andrews.ac.uk/~history/HistTopics/Prime_numbers.html # sub findgteprime { # find the smallest prime integer greater than or equal to use integer; my $num = ceil(shift); return 2 if $num <= 2; $num++ unless $num % 2; my $i; my $sqrtnum = int sqrt $num; my $sqrtnumsquared = $sqrtnum * $sqrtnum; NUM: for (;; $num += 2) { if ($sqrtnumsquared < $num) { $sqrtnum++; $sqrtnumsquared = $sqrtnum * $sqrtnum; } for ($i = 3; $i <= $sqrtnum; $i += 2) { next NUM unless $num % $i; } return $num; } } 1; package Tie::StdHandle; use strict; use Tie::Handle; our @ISA = 'Tie::Handle'; our $VERSION = '4.5'; sub TIEHANDLE { my $class = shift; my $fh = \do { local *HANDLE}; bless $fh,$class; $fh->OPEN(@_) if (@_); return $fh; } sub EOF { eof($_[0]) } sub TELL { tell($_[0]) } sub FILENO { fileno($_[0]) } sub SEEK { seek($_[0],$_[1],$_[2]) } sub CLOSE { close($_[0]) } sub BINMODE { binmode($_[0]) } sub OPEN { $_[0]->CLOSE if defined($_[0]->FILENO); @_ == 2 ? open($_[0], $_[1]) : open($_[0], $_[1], $_[2]); } sub READ { &CORE::read(shift, \shift, @_) } sub READLINE { my $fh = $_[0]; <$fh> } sub GETC { getc($_[0]) } sub WRITE { my $fh = $_[0]; local $\; # don't print any line terminator print $fh substr($_[1], $_[3], $_[2]); } 1; use strict; package Tie::Memoize; use Tie::Hash; our @ISA = 'Tie::ExtraHash'; our $VERSION = '1.1'; our $exists_token = \undef; sub croak {require Carp; goto &Carp::croak} # Format: [0: STORAGE, 1: EXISTS-CACHE, 2: FETCH_function; # 3: EXISTS_function, 4: DATA, 5: EXISTS_different ] sub FETCH { my ($h,$key) = ($_[0][0], $_[1]); my $res = $h->{$key}; return $res if defined $res; # Shortcut if accessible return $res if exists $h->{$key}; # Accessible, but undef my $cache = $_[0][1]{$key}; return if defined $cache and not $cache; # Known to not exist my @res = $_[0][2]->($key, $_[0][4]); # Autoload $_[0][1]{$key} = 0, return unless @res; # Cache non-existence delete $_[0][1]{$key}; # Clear existence cache, not needed any more $_[0][0]{$key} = $res[0]; # Store data and return } sub EXISTS { my ($a,$key) = (shift, shift); return 1 if exists $a->[0]{$key}; # Have data my $cache = $a->[1]{$key}; return $cache if defined $cache; # Existence cache my @res = $a->[3]($key,$a->[4]); $a->[1]{$key} = 0, return unless @res; # Cache non-existence # Now we know it exists return ($a->[1]{$key} = 1) if $a->[5]; # Only existence reported # Now know the value $a->[0]{$key} = $res[0]; # Store data return 1 } sub TIEHASH { croak 'syntax: tie %hash, \'Tie::AutoLoad\', \&fetch_subr' if @_ < 2; croak 'syntax: tie %hash, \'Tie::AutoLoad\', \&fetch_subr, $data, \&exists_subr, \%data_cache, \%existence_cache' if @_ > 6; push @_, undef if @_ < 3; # Data push @_, $_[1] if @_ < 4; # exists push @_, {} while @_ < 6; # initial value and caches bless [ @_[4,5,1,3,2], $_[1] ne $_[3]], $_[0] } 1; package Tie::File; require 5.005; use Carp ':DEFAULT', 'confess'; use POSIX 'SEEK_SET'; use Fcntl 'O_CREAT', 'O_RDWR', 'LOCK_EX', 'LOCK_SH', 'O_WRONLY', 'O_RDONLY'; sub O_ACCMODE () { O_RDONLY | O_RDWR | O_WRONLY } $VERSION = "1.02"; my $DEFAULT_MEMORY_SIZE = 1<<21; # 2 megabytes my $DEFAULT_AUTODEFER_THRESHHOLD = 3; # 3 records my $DEFAULT_AUTODEFER_FILELEN_THRESHHOLD = 65536; # 16 disk blocksful my %good_opt = map {$_ => 1, "-$_" => 1} qw(memory dw_size mode recsep discipline autodefer autochomp autodefer_threshhold concurrent); sub TIEARRAY { if (@_ % 2 != 0) { croak "usage: tie \@array, $_[0], filename, [option => value]..."; } my ($pack, $file, %opts) = @_; # transform '-foo' keys into 'foo' keys for my $key (keys %opts) { unless ($good_opt{$key}) { croak("$pack: Unrecognized option '$key'\n"); } my $okey = $key; if ($key =~ s/^-+//) { $opts{$key} = delete $opts{$okey}; } } if ($opts{concurrent}) { croak("$pack: concurrent access not supported yet\n"); } unless (defined $opts{memory}) { # default is the larger of the default cache size and the # deferred-write buffer size (if specified) $opts{memory} = $DEFAULT_MEMORY_SIZE; $opts{memory} = $opts{dw_size} if defined $opts{dw_size} && $opts{dw_size} > $DEFAULT_MEMORY_SIZE; # Dora Winifred Read } $opts{dw_size} = $opts{memory} unless defined $opts{dw_size}; if ($opts{dw_size} > $opts{memory}) { croak("$pack: dw_size may not be larger than total memory allocation\n"); } # are we in deferred-write mode? $opts{defer} = 0 unless defined $opts{defer}; $opts{deferred} = {}; # no records are presently deferred $opts{deferred_s} = 0; # count of total bytes in ->{deferred} $opts{deferred_max} = -1; # empty # What's a good way to arrange that this class can be overridden? $opts{cache} = Tie::File::Cache->new($opts{memory}); # autodeferment is enabled by default $opts{autodefer} = 1 unless defined $opts{autodefer}; $opts{autodeferring} = 0; # but is not initially active $opts{ad_history} = []; $opts{autodefer_threshhold} = $DEFAULT_AUTODEFER_THRESHHOLD unless defined $opts{autodefer_threshhold}; $opts{autodefer_filelen_threshhold} = $DEFAULT_AUTODEFER_FILELEN_THRESHHOLD unless defined $opts{autodefer_filelen_threshhold}; $opts{offsets} = [0]; $opts{filename} = $file; unless (defined $opts{recsep}) { $opts{recsep} = _default_recsep(); } $opts{recseplen} = length($opts{recsep}); if ($opts{recseplen} == 0) { croak "Empty record separator not supported by $pack"; } $opts{autochomp} = 1 unless defined $opts{autochomp}; $opts{mode} = O_CREAT|O_RDWR unless defined $opts{mode}; $opts{rdonly} = (($opts{mode} & O_ACCMODE) == O_RDONLY); $opts{sawlastrec} = undef; my $fh; if (UNIVERSAL::isa($file, 'GLOB')) { # We use 1 here on the theory that some systems # may not indicate failure if we use 0. # MSWin32 does not indicate failure with 0, but I don't know if # it will indicate failure with 1 or not. unless (seek $file, 1, SEEK_SET) { croak "$pack: your filehandle does not appear to be seekable"; } seek $file, 0, SEEK_SET; # put it back $fh = $file; # setting binmode is the user's problem } elsif (ref $file) { croak "usage: tie \@array, $pack, filename, [option => value]..."; } else { # $fh = \do { local *FH }; # XXX this is buggy if ($] < 5.006) { # perl 5.005 and earlier don't autovivify filehandles require Symbol; $fh = Symbol::gensym(); } sysopen $fh, $file, $opts{mode}, 0666 or return; binmode $fh; ++$opts{ourfh}; } { my $ofh = select $fh; $| = 1; select $ofh } # autoflush on write if (defined $opts{discipline} && $] >= 5.006) { # This avoids a compile-time warning under 5.005 eval 'binmode($fh, $opts{discipline})'; croak $@ if $@ =~ /unknown discipline/i; die if $@; } $opts{fh} = $fh; bless \%opts => $pack; } sub FETCH { my ($self, $n) = @_; my $rec; # check the defer buffer $rec = $self->{deferred}{$n} if exists $self->{deferred}{$n}; $rec = $self->_fetch($n) unless defined $rec; # inlined _chomp1 substr($rec, - $self->{recseplen}) = "" if defined $rec && $self->{autochomp}; $rec; } # Chomp many records in-place; return nothing useful sub _chomp { my $self = shift; return unless $self->{autochomp}; if ($self->{autochomp}) { for (@_) { next unless defined; substr($_, - $self->{recseplen}) = ""; } } } # Chomp one record in-place; return modified record sub _chomp1 { my ($self, $rec) = @_; return $rec unless $self->{autochomp}; return unless defined $rec; substr($rec, - $self->{recseplen}) = ""; $rec; } sub _fetch { my ($self, $n) = @_; # check the record cache { my $cached = $self->{cache}->lookup($n); return $cached if defined $cached; } if ($#{$self->{offsets}} < $n) { return if $self->{eof}; # request for record beyond end of file my $o = $self->_fill_offsets_to($n); # If it's still undefined, there is no such record, so return 'undef' return unless defined $o; } my $fh = $self->{FH}; $self->_seek($n); # we can do this now that offsets is populated my $rec = $self->_read_record; # If we happen to have just read the first record, check to see if # the length of the record matches what 'tell' says. If not, Tie::File # won't work, and should drop dead. # # if ($n == 0 && defined($rec) && tell($self->{fh}) != length($rec)) { # if (defined $self->{discipline}) { # croak "I/O discipline $self->{discipline} not supported"; # } else { # croak "File encoding not supported"; # } # } $self->{cache}->insert($n, $rec) if defined $rec && not $self->{flushing}; $rec; } sub STORE { my ($self, $n, $rec) = @_; die "STORE called from _check_integrity!" if $DIAGNOSTIC; $self->_fixrecs($rec); if ($self->{autodefer}) { $self->_annotate_ad_history($n); } return $self->_store_deferred($n, $rec) if $self->_is_deferring; # We need this to decide whether the new record will fit # It incidentally populates the offsets table # Note we have to do this before we alter the cache # 20020324 Wait, but this DOES alter the cache. TODO BUG? my $oldrec = $self->_fetch($n); if (not defined $oldrec) { # We're storing a record beyond the end of the file $self->_extend_file_to($n+1); $oldrec = $self->{recsep}; } # return if $oldrec eq $rec; # don't bother my $len_diff = length($rec) - length($oldrec); # length($oldrec) here is not consistent with text mode TODO XXX BUG $self->_mtwrite($rec, $self->{offsets}[$n], length($oldrec)); $self->_oadjust([$n, 1, $rec]); $self->{cache}->update($n, $rec); } sub _store_deferred { my ($self, $n, $rec) = @_; $self->{cache}->remove($n); my $old_deferred = $self->{deferred}{$n}; if (defined $self->{deferred_max} && $n > $self->{deferred_max}) { $self->{deferred_max} = $n; } $self->{deferred}{$n} = $rec; my $len_diff = length($rec); $len_diff -= length($old_deferred) if defined $old_deferred; $self->{deferred_s} += $len_diff; $self->{cache}->adj_limit(-$len_diff); if ($self->{deferred_s} > $self->{dw_size}) { $self->_flush; } elsif ($self->_cache_too_full) { $self->_cache_flush; } } # Remove a single record from the deferred-write buffer without writing it # The record need not be present sub _delete_deferred { my ($self, $n) = @_; my $rec = delete $self->{deferred}{$n}; return unless defined $rec; if (defined $self->{deferred_max} && $n == $self->{deferred_max}) { undef $self->{deferred_max}; } $self->{deferred_s} -= length $rec; $self->{cache}->adj_limit(length $rec); } sub FETCHSIZE { my $self = shift; my $n = $self->{eof} ? $#{$self->{offsets}} : $self->_fill_offsets; my $top_deferred = $self->_defer_max; $n = $top_deferred+1 if defined $top_deferred && $n < $top_deferred+1; $n; } sub STORESIZE { my ($self, $len) = @_; if ($self->{autodefer}) { $self->_annotate_ad_history('STORESIZE'); } my $olen = $self->FETCHSIZE; return if $len == $olen; # Woo-hoo! # file gets longer if ($len > $olen) { if ($self->_is_deferring) { for ($olen .. $len-1) { $self->_store_deferred($_, $self->{recsep}); } } else { $self->_extend_file_to($len); } return; } # file gets shorter if ($self->_is_deferring) { # TODO maybe replace this with map-plus-assignment? for (grep $_ >= $len, keys %{$self->{deferred}}) { $self->_delete_deferred($_); } $self->{deferred_max} = $len-1; } $self->_seek($len); $self->_chop_file; $#{$self->{offsets}} = $len; # $self->{offsets}[0] = 0; # in case we just chopped this $self->{cache}->remove(grep $_ >= $len, $self->{cache}->ckeys); } ### OPTIMIZE ME ### It should not be necessary to do FETCHSIZE ### Just seek to the end of the file. sub PUSH { my $self = shift; $self->SPLICE($self->FETCHSIZE, scalar(@_), @_); # No need to return: # $self->FETCHSIZE; # because av.c takes care of this for me } sub POP { my $self = shift; my $size = $self->FETCHSIZE; return if $size == 0; # print STDERR "# POPPITY POP POP POP\n"; scalar $self->SPLICE($size-1, 1); } sub SHIFT { my $self = shift; scalar $self->SPLICE(0, 1); } sub UNSHIFT { my $self = shift; $self->SPLICE(0, 0, @_); # $self->FETCHSIZE; # av.c takes care of this for me } sub CLEAR { my $self = shift; if ($self->{autodefer}) { $self->_annotate_ad_history('CLEAR'); } $self->_seekb(0); $self->_chop_file; $self->{cache}->set_limit($self->{memory}); $self->{cache}->empty; @{$self->{offsets}} = (0); %{$self->{deferred}}= (); $self->{deferred_s} = 0; $self->{deferred_max} = -1; } sub EXTEND { my ($self, $n) = @_; # No need to pre-extend anything in this case return if $self->_is_deferring; $self->_fill_offsets_to($n); $self->_extend_file_to($n); } sub DELETE { my ($self, $n) = @_; if ($self->{autodefer}) { $self->_annotate_ad_history('DELETE'); } my $lastrec = $self->FETCHSIZE-1; my $rec = $self->FETCH($n); $self->_delete_deferred($n) if $self->_is_deferring; if ($n == $lastrec) { $self->_seek($n); $self->_chop_file; $#{$self->{offsets}}--; $self->{cache}->remove($n); # perhaps in this case I should also remove trailing null records? # 20020316 # Note that delete @a[-3..-1] deletes the records in the wrong order, # so we only chop the very last one out of the file. We could repair this # by tracking deleted records inside the object. } elsif ($n < $lastrec) { $self->STORE($n, ""); } $rec; } sub EXISTS { my ($self, $n) = @_; return 1 if exists $self->{deferred}{$n}; $n < $self->FETCHSIZE; } sub SPLICE { my $self = shift; if ($self->{autodefer}) { $self->_annotate_ad_history('SPLICE'); } $self->_flush if $self->_is_deferring; # move this up? if (wantarray) { $self->_chomp(my @a = $self->_splice(@_)); @a; } else { $self->_chomp1(scalar $self->_splice(@_)); } } sub DESTROY { my $self = shift; $self->flush if $self->_is_deferring; $self->{cache}->delink if defined $self->{cache}; # break circular link if ($self->{fh} and $self->{ourfh}) { delete $self->{ourfh}; close delete $self->{fh}; } } sub _splice { my ($self, $pos, $nrecs, @data) = @_; my @result; $pos = 0 unless defined $pos; # Deal with negative and other out-of-range positions # Also set default for $nrecs { my $oldsize = $self->FETCHSIZE; $nrecs = $oldsize unless defined $nrecs; my $oldpos = $pos; if ($pos < 0) { $pos += $oldsize; if ($pos < 0) { croak "Modification of non-creatable array value attempted, " . "subscript $oldpos"; } } if ($pos > $oldsize) { return unless @data; $pos = $oldsize; # This is what perl does for normal arrays } # The manual is very unclear here if ($nrecs < 0) { $nrecs = $oldsize - $pos + $nrecs; $nrecs = 0 if $nrecs < 0; } # nrecs is too big---it really means "until the end" # 20030507 if ($nrecs + $pos > $oldsize) { $nrecs = $oldsize - $pos; } } $self->_fixrecs(@data); my $data = join '', @data; my $datalen = length $data; my $oldlen = 0; # compute length of data being removed for ($pos .. $pos+$nrecs-1) { last unless defined $self->_fill_offsets_to($_); my $rec = $self->_fetch($_); last unless defined $rec; push @result, $rec; # Why don't we just use length($rec) here? # Because that record might have come from the cache. _splice # might have been called to flush out the deferred-write records, # and in this case length($rec) is the length of the record to be # *written*, not the length of the actual record in the file. But # the offsets are still true. 20020322 $oldlen += $self->{offsets}[$_+1] - $self->{offsets}[$_] if defined $self->{offsets}[$_+1]; } $self->_fill_offsets_to($pos+$nrecs); # Modify the file $self->_mtwrite($data, $self->{offsets}[$pos], $oldlen); # Adjust the offsets table $self->_oadjust([$pos, $nrecs, @data]); { # Take this read cache stuff out into a separate function # You made a half-attempt to put it into _oadjust. # Finish something like that up eventually. # STORE also needs to do something similarish # update the read cache, part 1 # modified records for ($pos .. $pos+$nrecs-1) { my $new = $data[$_-$pos]; if (defined $new) { $self->{cache}->update($_, $new); } else { $self->{cache}->remove($_); } } # update the read cache, part 2 # moved records - records past the site of the change # need to be renumbered # Maybe merge this with the previous block? { my @oldkeys = grep $_ >= $pos + $nrecs, $self->{cache}->ckeys; my @newkeys = map $_-$nrecs+@data, @oldkeys; $self->{cache}->rekey(\@oldkeys, \@newkeys); } # Now there might be too much data in the cache, if we spliced out # some short records and spliced in some long ones. If so, flush # the cache. $self->_cache_flush; } # Yes, the return value of 'splice' *is* actually this complicated wantarray ? @result : @result ? $result[-1] : undef; } # write data into the file # $data is the data to be written. # it should be written at position $pos, and should overwrite # exactly $len of the following bytes. # Note that if length($data) > $len, the subsequent bytes will have to # be moved up, and if length($data) < $len, they will have to # be moved down sub _twrite { my ($self, $data, $pos, $len) = @_; unless (defined $pos) { die "\$pos was undefined in _twrite"; } my $len_diff = length($data) - $len; if ($len_diff == 0) { # Woo-hoo! my $fh = $self->{fh}; $self->_seekb($pos); $self->_write_record($data); return; # well, that was easy. } # the two records are of different lengths # our strategy here: rewrite the tail of the file, # reading ahead one buffer at a time # $bufsize is required to be at least as large as the data we're overwriting my $bufsize = _bufsize($len_diff); my ($writepos, $readpos) = ($pos, $pos+$len); my $next_block; my $more_data; # Seems like there ought to be a way to avoid the repeated code # and the special case here. The read(1) is also a little weird. # Think about this. do { $self->_seekb($readpos); my $br = read $self->{fh}, $next_block, $bufsize; $more_data = read $self->{fh}, my($dummy), 1; $self->_seekb($writepos); $self->_write_record($data); $readpos += $br; $writepos += length $data; $data = $next_block; } while $more_data; $self->_seekb($writepos); $self->_write_record($next_block); # There might be leftover data at the end of the file $self->_chop_file if $len_diff < 0; } # _iwrite(D, S, E) # Insert text D at position S. # Let C = E-S-|D|. If C < 0; die. # Data in [S,S+C) is copied to [S+D,S+D+C) = [S+D,E). # Data in [S+C = E-D, E) is returned. Data in [E, oo) is untouched. # # In a later version, don't read the entire intervening area into # memory at once; do the copying block by block. sub _iwrite { my $self = shift; my ($D, $s, $e) = @_; my $d = length $D; my $c = $e-$s-$d; local *FH = $self->{fh}; confess "Not enough space to insert $d bytes between $s and $e" if $c < 0; confess "[$s,$e) is an invalid insertion range" if $e < $s; $self->_seekb($s); read FH, my $buf, $e-$s; $D .= substr($buf, 0, $c, ""); $self->_seekb($s); $self->_write_record($D); return $buf; } # Like _twrite, but the data-pos-len triple may be repeated; you may # write several chunks. All the writing will be done in # one pass. Chunks SHALL be in ascending order and SHALL NOT overlap. sub _mtwrite { my $self = shift; my $unwritten = ""; my $delta = 0; @_ % 3 == 0 or die "Arguments to _mtwrite did not come in groups of three"; while (@_) { my ($data, $pos, $len) = splice @_, 0, 3; my $end = $pos + $len; # The OLD end of the segment to be replaced $data = $unwritten . $data; $delta -= length($unwritten); $unwritten = ""; $pos += $delta; # This is where the data goes now my $dlen = length $data; $self->_seekb($pos); if ($len >= $dlen) { # the data will fit $self->_write_record($data); $delta += ($dlen - $len); # everything following moves down by this much $data = ""; # All the data in the buffer has been written } else { # won't fit my $writable = substr($data, 0, $len - $delta, ""); $self->_write_record($writable); $delta += ($dlen - $len); # everything following moves down by this much } # At this point we've written some but maybe not all of the data. # There might be a gap to close up, or $data might still contain a # bunch of unwritten data that didn't fit. my $ndlen = length $data; if ($delta == 0) { $self->_write_record($data); } elsif ($delta < 0) { # upcopy (close up gap) if (@_) { $self->_upcopy($end, $end + $delta, $_[1] - $end); } else { $self->_upcopy($end, $end + $delta); } } else { # downcopy (insert data that didn't fit; replace this data in memory # with _later_ data that doesn't fit) if (@_) { $unwritten = $self->_downcopy($data, $end, $_[1] - $end); } else { # Make the file longer to accommodate the last segment that doesn't $unwritten = $self->_downcopy($data, $end); } } } } # Copy block of data of length $len from position $spos to position $dpos # $dpos must be <= $spos # # If $len is undefined, go all the way to the end of the file # and then truncate it ($spos - $dpos bytes will be removed) sub _upcopy { my $blocksize = 8192; my ($self, $spos, $dpos, $len) = @_; if ($dpos > $spos) { die "source ($spos) was upstream of destination ($dpos) in _upcopy"; } elsif ($dpos == $spos) { return; } while (! defined ($len) || $len > 0) { my $readsize = ! defined($len) ? $blocksize : $len > $blocksize ? $blocksize : $len; my $fh = $self->{fh}; $self->_seekb($spos); my $bytes_read = read $fh, my($data), $readsize; $self->_seekb($dpos); if ($data eq "") { $self->_chop_file; last; } $self->_write_record($data); $spos += $bytes_read; $dpos += $bytes_read; $len -= $bytes_read if defined $len; } } # Write $data into a block of length $len at position $pos, # moving everything in the block forwards to make room. # Instead of writing the last length($data) bytes from the block # (because there isn't room for them any longer) return them. # # Undefined $len means 'until the end of the file' sub _downcopy { my $blocksize = 8192; my ($self, $data, $pos, $len) = @_; my $fh = $self->{fh}; while (! defined $len || $len > 0) { my $readsize = ! defined($len) ? $blocksize : $len > $blocksize? $blocksize : $len; $self->_seekb($pos); read $fh, my($old), $readsize; my $last_read_was_short = length($old) < $readsize; $data .= $old; my $writable; if ($last_read_was_short) { # If last read was short, then $data now contains the entire rest # of the file, so there's no need to write only one block of it $writable = $data; $data = ""; } else { $writable = substr($data, 0, $readsize, ""); } last if $writable eq ""; $self->_seekb($pos); $self->_write_record($writable); last if $last_read_was_short && $data eq ""; $len -= $readsize if defined $len; $pos += $readsize; } return $data; } # Adjust the object data structures following an '_mtwrite' # Arguments are # [$pos, $nrecs, @length] items # indicating that $nrecs records were removed at $recpos (a record offset) # and replaced with records of length @length... # Arguments guarantee that $recpos is strictly increasing. # No return value sub _oadjust { my $self = shift; my $delta = 0; my $delta_recs = 0; my $prev_end = -1; my %newkeys; for (@_) { my ($pos, $nrecs, @data) = @$_; $pos += $delta_recs; # Adjust the offsets of the records after the previous batch up # to the first new one of this batch for my $i ($prev_end+2 .. $pos - 1) { $self->{offsets}[$i] += $delta; $newkey{$i} = $i + $delta_recs; } $prev_end = $pos + @data - 1; # last record moved on this pass # Remove the offsets for the removed records; # replace with the offsets for the inserted records my @newoff = ($self->{offsets}[$pos] + $delta); for my $i (0 .. $#data) { my $newlen = length $data[$i]; push @newoff, $newoff[$i] + $newlen; $delta += $newlen; } for my $i ($pos .. $pos+$nrecs-1) { last if $i+1 > $#{$self->{offsets}}; my $oldlen = $self->{offsets}[$i+1] - $self->{offsets}[$i]; $delta -= $oldlen; } # # also this data has changed, so update it in the cache # for (0 .. $#data) { # $self->{cache}->update($pos + $_, $data[$_]); # } # if ($delta_recs) { # my @oldkeys = grep $_ >= $pos + @data, $self->{cache}->ckeys; # my @newkeys = map $_ + $delta_recs, @oldkeys; # $self->{cache}->rekey(\@oldkeys, \@newkeys); # } # replace old offsets with new splice @{$self->{offsets}}, $pos, $nrecs+1, @newoff; # What if we just spliced out the end of the offsets table? # shouldn't we clear $self->{eof}? Test for this XXX BUG TODO $delta_recs += @data - $nrecs; # net change in total number of records } # The trailing records at the very end of the file if ($delta) { for my $i ($prev_end+2 .. $#{$self->{offsets}}) { $self->{offsets}[$i] += $delta; } } # If we scrubbed out all known offsets, regenerate the trivial table # that knows that the file does indeed start at 0. $self->{offsets}[0] = 0 unless @{$self->{offsets}}; # If the file got longer, the offsets table is no longer complete # $self->{eof} = 0 if $delta_recs > 0; # Now there might be too much data in the cache, if we spliced out # some short records and spliced in some long ones. If so, flush # the cache. $self->_cache_flush; } # If a record does not already end with the appropriate terminator # string, append one. sub _fixrecs { my $self = shift; for (@_) { $_ = "" unless defined $_; $_ .= $self->{recsep} unless substr($_, - $self->{recseplen}) eq $self->{recsep}; } } ################################################################ # # Basic read, write, and seek # # seek to the beginning of record #$n # Assumes that the offsets table is already correctly populated # # Note that $n=-1 has a special meaning here: It means the start of # the last known record; this may or may not be the very last record # in the file, depending on whether the offsets table is fully populated. # sub _seek { my ($self, $n) = @_; my $o = $self->{offsets}[$n]; defined($o) or confess("logic error: undefined offset for record $n"); seek $self->{fh}, $o, SEEK_SET or confess "Couldn't seek filehandle: $!"; # "Should never happen." } # seek to byte $b in the file sub _seekb { my ($self, $b) = @_; seek $self->{fh}, $b, SEEK_SET or die "Couldn't seek filehandle: $!"; # "Should never happen." } # populate the offsets table up to the beginning of record $n # return the offset of record $n sub _fill_offsets_to { my ($self, $n) = @_; return $self->{offsets}[$n] if $self->{eof}; my $fh = $self->{fh}; local *OFF = $self->{offsets}; my $rec; until ($#OFF >= $n) { $self->_seek(-1); # tricky -- see comment at _seek $rec = $self->_read_record; if (defined $rec) { push @OFF, int(tell $fh); # Tels says that int() saves memory here } else { $self->{eof} = 1; return; # It turns out there is no such record } } # we have now read all the records up to record n-1, # so we can return the offset of record n $OFF[$n]; } sub _fill_offsets { my ($self) = @_; my $fh = $self->{fh}; local *OFF = $self->{offsets}; $self->_seek(-1); # tricky -- see comment at _seek # Tels says that inlining read_record() would make this loop # five times faster. 20030508 while ( defined $self->_read_record()) { # int() saves us memory here push @OFF, int(tell $fh); } $self->{eof} = 1; $#OFF; } # assumes that $rec is already suitably terminated sub _write_record { my ($self, $rec) = @_; my $fh = $self->{fh}; local $\ = ""; print $fh $rec or die "Couldn't write record: $!"; # "Should never happen." # $self->{_written} += length($rec); } sub _read_record { my $self = shift; my $rec; { local $/ = $self->{recsep}; my $fh = $self->{fh}; $rec = <$fh>; } return unless defined $rec; if (substr($rec, -$self->{recseplen}) ne $self->{recsep}) { # improperly terminated final record --- quietly fix it. # my $ac = substr($rec, -$self->{recseplen}); # $ac =~ s/\n/\\n/g; $self->{sawlastrec} = 1; unless ($self->{rdonly}) { local $\ = ""; my $fh = $self->{fh}; print $fh $self->{recsep}; } $rec .= $self->{recsep}; } # $self->{_read} += length($rec) if defined $rec; $rec; } sub _rw_stats { my $self = shift; @{$self}{'_read', '_written'}; } ################################################################ # # Read cache management sub _cache_flush { my ($self) = @_; $self->{cache}->reduce_size_to($self->{memory} - $self->{deferred_s}); } sub _cache_too_full { my $self = shift; $self->{cache}->bytes + $self->{deferred_s} >= $self->{memory}; } ################################################################ # # File custodial services # # We have read to the end of the file and have the offsets table # entirely populated. Now we need to write a new record beyond # the end of the file. We prepare for this by writing # empty records into the file up to the position we want # # assumes that the offsets table already contains the offset of record $n, # if it exists, and extends to the end of the file if not. sub _extend_file_to { my ($self, $n) = @_; $self->_seek(-1); # position after the end of the last record my $pos = $self->{offsets}[-1]; # the offsets table has one entry more than the total number of records my $extras = $n - $#{$self->{offsets}}; # Todo : just use $self->{recsep} x $extras here? while ($extras-- > 0) { $self->_write_record($self->{recsep}); push @{$self->{offsets}}, int(tell $self->{fh}); } } # Truncate the file at the current position sub _chop_file { my $self = shift; truncate $self->{fh}, tell($self->{fh}); } # compute the size of a buffer suitable for moving # all the data in a file forward $n bytes # ($n may be negative) # The result should be at least $n. sub _bufsize { my $n = shift; return 8192 if $n <= 0; my $b = $n & ~8191; $b += 8192 if $n & 8191; $b; } ################################################################ # # Miscellaneous public methods # # Lock the file sub flock { my ($self, $op) = @_; unless (@_ <= 3) { my $pack = ref $self; croak "Usage: $pack\->flock([OPERATION])"; } my $fh = $self->{fh}; $op = LOCK_EX unless defined $op; my $locked = flock $fh, $op; if ($locked && ($op & (LOCK_EX | LOCK_SH))) { # If you're locking the file, then presumably it's because # there might have been a write access by another process. # In that case, the read cache contents and the offsets table # might be invalid, so discard them. 20030508 $self->{offsets} = [0]; $self->{cache}->empty; } $locked; } # Get/set autochomp option sub autochomp { my $self = shift; if (@_) { my $old = $self->{autochomp}; $self->{autochomp} = shift; $old; } else { $self->{autochomp}; } } # Get offset table entries; returns offset of nth record sub offset { my ($self, $n) = @_; if ($#{$self->{offsets}} < $n) { return if $self->{eof}; # request for record beyond the end of file my $o = $self->_fill_offsets_to($n); # If it's still undefined, there is no such record, so return 'undef' return unless defined $o; } $self->{offsets}[$n]; } sub discard_offsets { my $self = shift; $self->{offsets} = [0]; } ################################################################ # # Matters related to deferred writing # # Defer writes sub defer { my $self = shift; $self->_stop_autodeferring; @{$self->{ad_history}} = (); $self->{defer} = 1; } # Flush deferred writes # # This could be better optimized to write the file in one pass, instead # of one pass per block of records. But that will require modifications # to _twrite, so I should have a good _twrite test suite first. sub flush { my $self = shift; $self->_flush; $self->{defer} = 0; } sub _old_flush { my $self = shift; my @writable = sort {$a<=>$b} (keys %{$self->{deferred}}); while (@writable) { # gather all consecutive records from the front of @writable my $first_rec = shift @writable; my $last_rec = $first_rec+1; ++$last_rec, shift @writable while @writable && $last_rec == $writable[0]; --$last_rec; $self->_fill_offsets_to($last_rec); $self->_extend_file_to($last_rec); $self->_splice($first_rec, $last_rec-$first_rec+1, @{$self->{deferred}}{$first_rec .. $last_rec}); } $self->_discard; # clear out defered-write-cache } sub _flush { my $self = shift; my @writable = sort {$a<=>$b} (keys %{$self->{deferred}}); my @args; my @adjust; while (@writable) { # gather all consecutive records from the front of @writable my $first_rec = shift @writable; my $last_rec = $first_rec+1; ++$last_rec, shift @writable while @writable && $last_rec == $writable[0]; --$last_rec; my $end = $self->_fill_offsets_to($last_rec+1); if (not defined $end) { $self->_extend_file_to($last_rec); $end = $self->{offsets}[$last_rec]; } my ($start) = $self->{offsets}[$first_rec]; push @args, join("", @{$self->{deferred}}{$first_rec .. $last_rec}), # data $start, # position $end-$start; # length push @adjust, [$first_rec, # starting at this position... $last_rec-$first_rec+1, # this many records... # are replaced with these... @{$self->{deferred}}{$first_rec .. $last_rec}, ]; } $self->_mtwrite(@args); # write multiple record groups $self->_discard; # clear out defered-write-cache $self->_oadjust(@adjust); } # Discard deferred writes and disable future deferred writes sub discard { my $self = shift; $self->_discard; $self->{defer} = 0; } # Discard deferred writes, but retain old deferred writing mode sub _discard { my $self = shift; %{$self->{deferred}} = (); $self->{deferred_s} = 0; $self->{deferred_max} = -1; $self->{cache}->set_limit($self->{memory}); } # Deferred writing is enabled, either explicitly ($self->{defer}) # or automatically ($self->{autodeferring}) sub _is_deferring { my $self = shift; $self->{defer} || $self->{autodeferring}; } # The largest record number of any deferred record sub _defer_max { my $self = shift; return $self->{deferred_max} if defined $self->{deferred_max}; my $max = -1; for my $key (keys %{$self->{deferred}}) { $max = $key if $key > $max; } $self->{deferred_max} = $max; $max; } ################################################################ # # Matters related to autodeferment # # Get/set autodefer option sub autodefer { my $self = shift; if (@_) { my $old = $self->{autodefer}; $self->{autodefer} = shift; if ($old) { $self->_stop_autodeferring; @{$self->{ad_history}} = (); } $old; } else { $self->{autodefer}; } } # The user is trying to store record #$n Record that in the history, # and then enable (or disable) autodeferment if that seems useful. # Note that it's OK for $n to be a non-number, as long as the function # is prepared to deal with that. Nobody else looks at the ad_history. # # Now, what does the ad_history mean, and what is this function doing? # Essentially, the idea is to enable autodeferring when we see that the # user has made three consecutive STORE calls to three consecutive records. # ("Three" is actually ->{autodefer_threshhold}.) # A STORE call for record #$n inserts $n into the autodefer history, # and if the history contains three consecutive records, we enable # autodeferment. An ad_history of [X, Y] means that the most recent # STOREs were for records X, X+1, ..., Y, in that order. # # Inserting a nonconsecutive number erases the history and starts over. # # Performing a special operation like SPLICE erases the history. # # There's one special case: CLEAR means that CLEAR was just called. # In this case, we prime the history with [-2, -1] so that if the next # write is for record 0, autodeferring goes on immediately. This is for # the common special case of "@a = (...)". # sub _annotate_ad_history { my ($self, $n) = @_; return unless $self->{autodefer}; # feature is disabled return if $self->{defer}; # already in explicit defer mode return unless $self->{offsets}[-1] >= $self->{autodefer_filelen_threshhold}; local *H = $self->{ad_history}; if ($n eq 'CLEAR') { @H = (-2, -1); # prime the history with fake records $self->_stop_autodeferring; } elsif ($n =~ /^\d+$/) { if (@H == 0) { @H = ($n, $n); } else { # @H == 2 if ($H[1] == $n-1) { # another consecutive record $H[1]++; if ($H[1] - $H[0] + 1 >= $self->{autodefer_threshhold}) { $self->{autodeferring} = 1; } } else { # nonconsecutive- erase and start over @H = ($n, $n); $self->_stop_autodeferring; } } } else { # SPLICE or STORESIZE or some such @H = (); $self->_stop_autodeferring; } } # If autodeferring was enabled, cut it out and discard the history sub _stop_autodeferring { my $self = shift; if ($self->{autodeferring}) { $self->_flush; } $self->{autodeferring} = 0; } ################################################################ # This is NOT a method. It is here for two reasons: # 1. To factor a fairly complicated block out of the constructor # 2. To provide access for the test suite, which need to be sure # files are being written properly. sub _default_recsep { my $recsep = $/; if ($^O eq 'MSWin32') { # Dos too? # Windows users expect files to be terminated with \r\n # But $/ is set to \n instead # Note that this also transforms \n\n into \r\n\r\n. # That is a feature. $recsep =~ s/\n/\r\n/g; } $recsep; } # Utility function for _check_integrity sub _ci_warn { my $msg = shift; $msg =~ s/\n/\\n/g; $msg =~ s/\r/\\r/g; print "# $msg\n"; } # Given a file, make sure the cache is consistent with the # file contents and the internal data structures are consistent with # each other. Returns true if everything checks out, false if not # # The $file argument is no longer used. It is retained for compatibility # with the existing test suite. sub _check_integrity { my ($self, $file, $warn) = @_; my $rsl = $self->{recseplen}; my $rs = $self->{recsep}; my $good = 1; local *_; # local $_ does not work here local $DIAGNOSTIC = 1; if (not defined $rs) { _ci_warn("recsep is undef!"); $good = 0; } elsif ($rs eq "") { _ci_warn("recsep is empty!"); $good = 0; } elsif ($rsl != length $rs) { my $ln = length $rs; _ci_warn("recsep <$rs> has length $ln, should be $rsl"); $good = 0; } if (not defined $self->{offsets}[0]) { _ci_warn("offset 0 is missing!"); $good = 0; } elsif ($self->{offsets}[0] != 0) { _ci_warn("rec 0: offset <$self->{offsets}[0]> s/b 0!"); $good = 0; } my $cached = 0; { local *F = $self->{fh}; seek F, 0, SEEK_SET; local $. = 0; local $/ = $rs; while () { my $n = $. - 1; my $cached = $self->{cache}->_produce($n); my $offset = $self->{offsets}[$.]; my $ao = tell F; if (defined $offset && $offset != $ao) { _ci_warn("rec $n: offset <$offset> actual <$ao>"); $good = 0; } if (defined $cached && $_ ne $cached && ! $self->{deferred}{$n}) { $good = 0; _ci_warn("rec $n: cached <$cached> actual <$_>"); } if (defined $cached && substr($cached, -$rsl) ne $rs) { $good = 0; _ci_warn("rec $n in the cache is missing the record separator"); } if (! defined $offset && $self->{eof}) { $good = 0; _ci_warn("The offset table was marked complete, but it is missing " . "element $."); } } if (@{$self->{offsets}} > $.+1) { $good = 0; my $n = @{$self->{offsets}}; _ci_warn("The offset table has $n items, but the file has only $."); } my $deferring = $self->_is_deferring; for my $n ($self->{cache}->ckeys) { my $r = $self->{cache}->_produce($n); $cached += length($r); next if $n+1 <= $.; # checked this already _ci_warn("spurious caching of record $n"); $good = 0; } my $b = $self->{cache}->bytes; if ($cached != $b) { _ci_warn("cache size is $b, should be $cached"); $good = 0; } } # That cache has its own set of tests $good = 0 unless $self->{cache}->_check_integrity; # Now let's check the deferbuffer # Unless deferred writing is enabled, it should be empty if (! $self->_is_deferring && %{$self->{deferred}}) { _ci_warn("deferred writing disabled, but deferbuffer nonempty"); $good = 0; } # Any record in the deferbuffer should *not* be present in the readcache my $deferred_s = 0; while (my ($n, $r) = each %{$self->{deferred}}) { $deferred_s += length($r); if (defined $self->{cache}->_produce($n)) { _ci_warn("record $n is in the deferbuffer *and* the readcache"); $good = 0; } if (substr($r, -$rsl) ne $rs) { _ci_warn("rec $n in the deferbuffer is missing the record separator"); $good = 0; } } # Total size of deferbuffer should match internal total if ($deferred_s != $self->{deferred_s}) { _ci_warn("buffer size is $self->{deferred_s}, should be $deferred_s"); $good = 0; } # Total size of deferbuffer should not exceed the specified limit if ($deferred_s > $self->{dw_size}) { _ci_warn("buffer size is $self->{deferred_s} which exceeds the limit " . "of $self->{dw_size}"); $good = 0; } # Total size of cached data should not exceed the specified limit if ($deferred_s + $cached > $self->{memory}) { my $total = $deferred_s + $cached; _ci_warn("total stored data size is $total which exceeds the limit " . "of $self->{memory}"); $good = 0; } # Stuff related to autodeferment if (!$self->{autodefer} && @{$self->{ad_history}}) { _ci_warn("autodefer is disabled, but ad_history is nonempty"); $good = 0; } if ($self->{autodeferring} && $self->{defer}) { _ci_warn("both autodeferring and explicit deferring are active"); $good = 0; } if (@{$self->{ad_history}} == 0) { # That's OK, no additional tests required } elsif (@{$self->{ad_history}} == 2) { my @non_number = grep !/^-?\d+$/, @{$self->{ad_history}}; if (@non_number) { my $msg; { local $" = ')('; $msg = "ad_history contains non-numbers (@{$self->{ad_history}})"; } _ci_warn($msg); $good = 0; } elsif ($self->{ad_history}[1] < $self->{ad_history}[0]) { _ci_warn("ad_history has nonsensical values @{$self->{ad_history}}"); $good = 0; } } else { _ci_warn("ad_history has bad length <@{$self->{ad_history}}>"); $good = 0; } $good; } ################################################################ # # Tie::File::Cache # # Read cache package Tie::File::Cache; $Tie::File::Cache::VERSION = $Tie::File::VERSION; use Carp ':DEFAULT', 'confess'; sub HEAP () { 0 } sub HASH () { 1 } sub MAX () { 2 } sub BYTES() { 3 } #sub STAT () { 4 } # Array with request statistics for each record #sub MISS () { 5 } # Total number of cache misses #sub REQ () { 6 } # Total number of cache requests use strict 'vars'; sub new { my ($pack, $max) = @_; local *_; croak "missing argument to ->new" unless defined $max; my $self = []; bless $self => $pack; @$self = (Tie::File::Heap->new($self), {}, $max, 0); $self; } sub adj_limit { my ($self, $n) = @_; $self->[MAX] += $n; } sub set_limit { my ($self, $n) = @_; $self->[MAX] = $n; } # For internal use only # Will be called by the heap structure to notify us that a certain # piece of data has moved from one heap element to another. # $k is the hash key of the item # $n is the new index into the heap at which it is stored # If $n is undefined, the item has been removed from the heap. sub _heap_move { my ($self, $k, $n) = @_; if (defined $n) { $self->[HASH]{$k} = $n; } else { delete $self->[HASH]{$k}; } } sub insert { my ($self, $key, $val) = @_; local *_; croak "missing argument to ->insert" unless defined $key; unless (defined $self->[MAX]) { confess "undefined max" ; } confess "undefined val" unless defined $val; return if length($val) > $self->[MAX]; # if ($self->[STAT]) { # $self->[STAT][$key] = 1; # return; # } my $oldnode = $self->[HASH]{$key}; if (defined $oldnode) { my $oldval = $self->[HEAP]->set_val($oldnode, $val); $self->[BYTES] -= length($oldval); } else { $self->[HEAP]->insert($key, $val); } $self->[BYTES] += length($val); $self->flush if $self->[BYTES] > $self->[MAX]; } sub expire { my $self = shift; my $old_data = $self->[HEAP]->popheap; return unless defined $old_data; $self->[BYTES] -= length $old_data; $old_data; } sub remove { my ($self, @keys) = @_; my @result; # if ($self->[STAT]) { # for my $key (@keys) { # $self->[STAT][$key] = 0; # } # return; # } for my $key (@keys) { next unless exists $self->[HASH]{$key}; my $old_data = $self->[HEAP]->remove($self->[HASH]{$key}); $self->[BYTES] -= length $old_data; push @result, $old_data; } @result; } sub lookup { my ($self, $key) = @_; local *_; croak "missing argument to ->lookup" unless defined $key; # if ($self->[STAT]) { # $self->[MISS]++ if $self->[STAT][$key]++ == 0; # $self->[REQ]++; # my $hit_rate = 1 - $self->[MISS] / $self->[REQ]; # # Do some testing to determine this threshhold # $#$self = STAT - 1 if $hit_rate > 0.20; # } if (exists $self->[HASH]{$key}) { $self->[HEAP]->lookup($self->[HASH]{$key}); } else { return; } } # For internal use only sub _produce { my ($self, $key) = @_; my $loc = $self->[HASH]{$key}; return unless defined $loc; $self->[HEAP][$loc][2]; } # For internal use only sub _promote { my ($self, $key) = @_; $self->[HEAP]->promote($self->[HASH]{$key}); } sub empty { my ($self) = @_; %{$self->[HASH]} = (); $self->[BYTES] = 0; $self->[HEAP]->empty; # @{$self->[STAT]} = (); # $self->[MISS] = 0; # $self->[REQ] = 0; } sub is_empty { my ($self) = @_; keys %{$self->[HASH]} == 0; } sub update { my ($self, $key, $val) = @_; local *_; croak "missing argument to ->update" unless defined $key; if (length($val) > $self->[MAX]) { my ($oldval) = $self->remove($key); $self->[BYTES] -= length($oldval) if defined $oldval; } elsif (exists $self->[HASH]{$key}) { my $oldval = $self->[HEAP]->set_val($self->[HASH]{$key}, $val); $self->[BYTES] += length($val); $self->[BYTES] -= length($oldval) if defined $oldval; } else { $self->[HEAP]->insert($key, $val); $self->[BYTES] += length($val); } $self->flush; } sub rekey { my ($self, $okeys, $nkeys) = @_; local *_; my %map; @map{@$okeys} = @$nkeys; croak "missing argument to ->rekey" unless defined $nkeys; croak "length mismatch in ->rekey arguments" unless @$nkeys == @$okeys; my %adjusted; # map new keys to heap indices # You should be able to cut this to one loop TODO XXX for (0 .. $#$okeys) { $adjusted{$nkeys->[$_]} = delete $self->[HASH]{$okeys->[$_]}; } while (my ($nk, $ix) = each %adjusted) { # @{$self->[HASH]}{keys %adjusted} = values %adjusted; $self->[HEAP]->rekey($ix, $nk); $self->[HASH]{$nk} = $ix; } } sub ckeys { my $self = shift; my @a = keys %{$self->[HASH]}; @a; } # Return total amount of cached data sub bytes { my $self = shift; $self->[BYTES]; } # Expire oldest item from cache until cache size is smaller than $max sub reduce_size_to { my ($self, $max) = @_; until ($self->[BYTES] <= $max) { # Note that Tie::File::Cache::expire has been inlined here my $old_data = $self->[HEAP]->popheap; return unless defined $old_data; $self->[BYTES] -= length $old_data; } } # Why not just $self->reduce_size_to($self->[MAX])? # Try this when things stabilize TODO XXX # If the cache is too full, expire the oldest records sub flush { my $self = shift; $self->reduce_size_to($self->[MAX]) if $self->[BYTES] > $self->[MAX]; } # For internal use only sub _produce_lru { my $self = shift; $self->[HEAP]->expire_order; } BEGIN { *_ci_warn = \&Tie::File::_ci_warn } sub _check_integrity { # For CACHE my $self = shift; my $good = 1; # Test HEAP $self->[HEAP]->_check_integrity or $good = 0; # Test HASH my $bytes = 0; for my $k (keys %{$self->[HASH]}) { if ($k ne '0' && $k !~ /^[1-9][0-9]*$/) { $good = 0; _ci_warn "Cache hash key <$k> is non-numeric"; } my $h = $self->[HASH]{$k}; if (! defined $h) { $good = 0; _ci_warn "Heap index number for key $k is undefined"; } elsif ($h == 0) { $good = 0; _ci_warn "Heap index number for key $k is zero"; } else { my $j = $self->[HEAP][$h]; if (! defined $j) { $good = 0; _ci_warn "Heap contents key $k (=> $h) are undefined"; } else { $bytes += length($j->[2]); if ($k ne $j->[1]) { $good = 0; _ci_warn "Heap contents key $k (=> $h) is $j->[1], should be $k"; } } } } # Test BYTES if ($bytes != $self->[BYTES]) { $good = 0; _ci_warn "Total data in cache is $bytes, expected $self->[BYTES]"; } # Test MAX if ($bytes > $self->[MAX]) { $good = 0; _ci_warn "Total data in cache is $bytes, exceeds maximum $self->[MAX]"; } return $good; } sub delink { my $self = shift; $self->[HEAP] = undef; # Bye bye heap } ################################################################ # # Tie::File::Heap # # Heap data structure for use by cache LRU routines package Tie::File::Heap; use Carp ':DEFAULT', 'confess'; $Tie::File::Heap::VERSION = $Tie::File::Cache::VERSION; sub SEQ () { 0 }; sub KEY () { 1 }; sub DAT () { 2 }; sub new { my ($pack, $cache) = @_; die "$pack: Parent cache object $cache does not support _heap_move method" unless eval { $cache->can('_heap_move') }; my $self = [[0,$cache,0]]; bless $self => $pack; } # Allocate a new sequence number, larger than all previously allocated numbers sub _nseq { my $self = shift; $self->[0][0]++; } sub _cache { my $self = shift; $self->[0][1]; } sub _nelts { my $self = shift; $self->[0][2]; } sub _nelts_inc { my $self = shift; ++$self->[0][2]; } sub _nelts_dec { my $self = shift; --$self->[0][2]; } sub is_empty { my $self = shift; $self->_nelts == 0; } sub empty { my $self = shift; $#$self = 0; $self->[0][2] = 0; $self->[0][0] = 0; # might as well reset the sequence numbers } # notify the parent cache object that we moved something sub _heap_move { my $self = shift; $self->_cache->_heap_move(@_); } # Insert a piece of data into the heap with the indicated sequence number. # The item with the smallest sequence number is always at the top. # If no sequence number is specified, allocate a new one and insert the # item at the bottom. sub insert { my ($self, $key, $data, $seq) = @_; $seq = $self->_nseq unless defined $seq; $self->_insert_new([$seq, $key, $data]); } # Insert a new, fresh item at the bottom of the heap sub _insert_new { my ($self, $item) = @_; my $i = @$self; $i = int($i/2) until defined $self->[$i/2]; $self->[$i] = $item; $self->[0][1]->_heap_move($self->[$i][KEY], $i); $self->_nelts_inc; } # Insert [$data, $seq] pair at or below item $i in the heap. # If $i is omitted, default to 1 (the top element.) sub _insert { my ($self, $item, $i) = @_; # $self->_check_loc($i) if defined $i; $i = 1 unless defined $i; until (! defined $self->[$i]) { if ($self->[$i][SEQ] > $item->[SEQ]) { # inserted item is older ($self->[$i], $item) = ($item, $self->[$i]); $self->[0][1]->_heap_move($self->[$i][KEY], $i); } # If either is undefined, go that way. Otherwise, choose at random my $dir; $dir = 0 if !defined $self->[2*$i]; $dir = 1 if !defined $self->[2*$i+1]; $dir = int(rand(2)) unless defined $dir; $i = 2*$i + $dir; } $self->[$i] = $item; $self->[0][1]->_heap_move($self->[$i][KEY], $i); $self->_nelts_inc; } # Remove the item at node $i from the heap, moving child items upwards. # The item with the smallest sequence number is always at the top. # Moving items upwards maintains this condition. # Return the removed item. Return undef if there was no item at node $i. sub remove { my ($self, $i) = @_; $i = 1 unless defined $i; my $top = $self->[$i]; return unless defined $top; while (1) { my $ii; my ($L, $R) = (2*$i, 2*$i+1); # If either is undefined, go the other way. # Otherwise, go towards the smallest. last unless defined $self->[$L] || defined $self->[$R]; $ii = $R if not defined $self->[$L]; $ii = $L if not defined $self->[$R]; unless (defined $ii) { $ii = $self->[$L][SEQ] < $self->[$R][SEQ] ? $L : $R; } $self->[$i] = $self->[$ii]; # Promote child to fill vacated spot $self->[0][1]->_heap_move($self->[$i][KEY], $i); $i = $ii; # Fill new vacated spot } $self->[0][1]->_heap_move($top->[KEY], undef); undef $self->[$i]; $self->_nelts_dec; return $top->[DAT]; } sub popheap { my $self = shift; $self->remove(1); } # set the sequence number of the indicated item to a higher number # than any other item in the heap, and bubble the item down to the # bottom. sub promote { my ($self, $n) = @_; # $self->_check_loc($n); $self->[$n][SEQ] = $self->_nseq; my $i = $n; while (1) { my ($L, $R) = (2*$i, 2*$i+1); my $dir; last unless defined $self->[$L] || defined $self->[$R]; $dir = $R unless defined $self->[$L]; $dir = $L unless defined $self->[$R]; unless (defined $dir) { $dir = $self->[$L][SEQ] < $self->[$R][SEQ] ? $L : $R; } @{$self}[$i, $dir] = @{$self}[$dir, $i]; for ($i, $dir) { $self->[0][1]->_heap_move($self->[$_][KEY], $_) if defined $self->[$_]; } $i = $dir; } } # Return item $n from the heap, promoting its LRU status sub lookup { my ($self, $n) = @_; # $self->_check_loc($n); my $val = $self->[$n]; $self->promote($n); $val->[DAT]; } # Assign a new value for node $n, promoting it to the bottom of the heap sub set_val { my ($self, $n, $val) = @_; # $self->_check_loc($n); my $oval = $self->[$n][DAT]; $self->[$n][DAT] = $val; $self->promote($n); return $oval; } # The hash key has changed for an item; # alter the heap's record of the hash key sub rekey { my ($self, $n, $new_key) = @_; # $self->_check_loc($n); $self->[$n][KEY] = $new_key; } sub _check_loc { my ($self, $n) = @_; unless (1 || defined $self->[$n]) { confess "_check_loc($n) failed"; } } BEGIN { *_ci_warn = \&Tie::File::_ci_warn } sub _check_integrity { my $self = shift; my $good = 1; my %seq; unless (eval {$self->[0][1]->isa("Tie::File::Cache")}) { _ci_warn "Element 0 of heap corrupt"; $good = 0; } $good = 0 unless $self->_satisfies_heap_condition(1); for my $i (2 .. $#{$self}) { my $p = int($i/2); # index of parent node if (defined $self->[$i] && ! defined $self->[$p]) { _ci_warn "Element $i of heap defined, but parent $p isn't"; $good = 0; } if (defined $self->[$i]) { if ($seq{$self->[$i][SEQ]}) { my $seq = $self->[$i][SEQ]; _ci_warn "Nodes $i and $seq{$seq} both have SEQ=$seq"; $good = 0; } else { $seq{$self->[$i][SEQ]} = $i; } } } return $good; } sub _satisfies_heap_condition { my $self = shift; my $n = shift || 1; my $good = 1; for (0, 1) { my $c = $n*2 + $_; next unless defined $self->[$c]; if ($self->[$n][SEQ] >= $self->[$c]) { _ci_warn "Node $n of heap does not predate node $c"; $good = 0 ; } $good = 0 unless $self->_satisfies_heap_condition($c); } return $good; } # Return a list of all the values, sorted by expiration order sub expire_order { my $self = shift; my @nodes = sort {$a->[SEQ] <=> $b->[SEQ]} $self->_nodes; map { $_->[KEY] } @nodes; } sub _nodes { my $self = shift; my $i = shift || 1; return unless defined $self->[$i]; ($self->[$i], $self->_nodes($i*2), $self->_nodes($i*2+1)); } "Cogito, ergo sum."; # don't forget to return a true value from the file __END__ package Tie::Hash; our $VERSION = '1.05'; use Carp; use warnings::register; sub new { my $pkg = shift; $pkg->TIEHASH(@_); } # Grandfather "new" sub TIEHASH { my $pkg = shift; my $pkg_new = $pkg -> can ('new'); if ($pkg_new and $pkg ne __PACKAGE__) { my $my_new = __PACKAGE__ -> can ('new'); if ($pkg_new == $my_new) { # # Prevent recursion # croak "$pkg must define either a TIEHASH() or a new() method"; } warnings::warnif ("WARNING: calling ${pkg}->new since " . "${pkg}->TIEHASH is missing"); $pkg -> new (@_); } else { croak "$pkg doesn't define a TIEHASH method"; } } sub EXISTS { my $pkg = ref $_[0]; croak "$pkg doesn't define an EXISTS method"; } sub CLEAR { my $self = shift; my $key = $self->FIRSTKEY(@_); my @keys; while (defined $key) { push @keys, $key; $key = $self->NEXTKEY(@_, $key); } foreach $key (@keys) { $self->DELETE(@_, $key); } } # The Tie::StdHash package implements standard perl hash behaviour. # It exists to act as a base class for classes which only wish to # alter some parts of their behaviour. package Tie::StdHash; # @ISA = qw(Tie::Hash); # would inherit new() only sub TIEHASH { bless {}, $_[0] } sub STORE { $_[0]->{$_[1]} = $_[2] } sub FETCH { $_[0]->{$_[1]} } sub FIRSTKEY { my $a = scalar keys %{$_[0]}; each %{$_[0]} } sub NEXTKEY { each %{$_[0]} } sub EXISTS { exists $_[0]->{$_[1]} } sub DELETE { delete $_[0]->{$_[1]} } sub CLEAR { %{$_[0]} = () } sub SCALAR { scalar %{$_[0]} } package Tie::ExtraHash; sub TIEHASH { my $p = shift; bless [{}, @_], $p } sub STORE { $_[0][0]{$_[1]} = $_[2] } sub FETCH { $_[0][0]{$_[1]} } sub FIRSTKEY { my $a = scalar keys %{$_[0][0]}; each %{$_[0][0]} } sub NEXTKEY { each %{$_[0][0]} } sub EXISTS { exists $_[0][0]->{$_[1]} } sub DELETE { delete $_[0][0]->{$_[1]} } sub CLEAR { %{$_[0][0]} = () } sub SCALAR { scalar %{$_[0][0]} } 1; package Tie::Handle; use 5.006_001; our $VERSION = '4.2'; # Tie::StdHandle used to be inside Tie::Handle. For backwards compatibility # loading Tie::Handle has to make Tie::StdHandle available. use Tie::StdHandle; use Carp; use warnings::register; sub new { my $pkg = shift; $pkg->TIEHANDLE(@_); } # "Grandfather" the new, a la Tie::Hash sub TIEHANDLE { my $pkg = shift; if (defined &{"{$pkg}::new"}) { warnings::warnif("WARNING: calling ${pkg}->new since ${pkg}->TIEHANDLE is missing"); $pkg->new(@_); } else { croak "$pkg doesn't define a TIEHANDLE method"; } } sub PRINT { my $self = shift; if($self->can('WRITE') != \&WRITE) { my $buf = join(defined $, ? $, : "",@_); $buf .= $\ if defined $\; $self->WRITE($buf,length($buf),0); } else { croak ref($self)," doesn't define a PRINT method"; } } sub PRINTF { my $self = shift; if($self->can('WRITE') != \&WRITE) { my $buf = sprintf(shift,@_); $self->WRITE($buf,length($buf),0); } else { croak ref($self)," doesn't define a PRINTF method"; } } sub READLINE { my $pkg = ref $_[0]; croak "$pkg doesn't define a READLINE method"; } sub GETC { my $self = shift; if($self->can('READ') != \&READ) { my $buf; $self->READ($buf,1); return $buf; } else { croak ref($self)," doesn't define a GETC method"; } } sub READ { my $pkg = ref $_[0]; croak "$pkg doesn't define a READ method"; } sub WRITE { my $pkg = ref $_[0]; croak "$pkg doesn't define a WRITE method"; } sub CLOSE { my $pkg = ref $_[0]; croak "$pkg doesn't define a CLOSE method"; } 1; package Tie::RefHash; use vars qw/$VERSION/; $VERSION = "1.39"; use 5.005; use Tie::Hash; use vars '@ISA'; @ISA = qw(Tie::Hash); use strict; use Carp qw/croak/; BEGIN { local $@; # determine whether we need to take care of threads use Config (); my $usethreads = $Config::Config{usethreads}; # && exists $INC{"threads.pm"} *_HAS_THREADS = $usethreads ? sub () { 1 } : sub () { 0 }; *_HAS_SCALAR_UTIL = eval { require Scalar::Util; 1 } ? sub () { 1 } : sub () { 0 }; *_HAS_WEAKEN = defined(&Scalar::Util::weaken) ? sub () { 1 } : sub () { 0 }; } BEGIN { # create a refaddr function local $@; if ( _HAS_SCALAR_UTIL ) { Scalar::Util->import("refaddr"); } else { require overload; *refaddr = sub { if ( overload::StrVal($_[0]) =~ /\( 0x ([a-zA-Z0-9]+) \)$/x) { return $1; } else { die "couldn't parse StrVal: " . overload::StrVal($_[0]); } }; } } my (@thread_object_registry, $count); # used by the CLONE method to rehash the keys after their refaddr changed sub TIEHASH { my $c = shift; my $s = []; bless $s, $c; while (@_) { $s->STORE(shift, shift); } if (_HAS_THREADS ) { if ( _HAS_WEAKEN ) { # remember the object so that we can rekey it on CLONE push @thread_object_registry, $s; # but make this a weak reference, so that there are no leaks Scalar::Util::weaken( $thread_object_registry[-1] ); if ( ++$count > 1000 ) { # this ensures we don't fill up with a huge array dead weakrefs @thread_object_registry = grep { defined } @thread_object_registry; $count = 0; } } else { $count++; # used in the warning } } return $s; } my $storable_format_version = join("/", __PACKAGE__, "0.01"); sub STORABLE_freeze { my ( $self, $is_cloning ) = @_; my ( $refs, $reg ) = @$self; return ( $storable_format_version, [ values %$refs ], $reg || {} ); } sub STORABLE_thaw { my ( $self, $is_cloning, $version, $refs, $reg ) = @_; croak "incompatible versions of Tie::RefHash between freeze and thaw" unless $version eq $storable_format_version; @$self = ( {}, $reg ); $self->_reindex_keys( $refs ); } sub CLONE { my $pkg = shift; if ( $count and not _HAS_WEAKEN ) { warn "Tie::RefHash is not threadsafe without Scalar::Util::weaken"; } # when the thread has been cloned all the objects need to be updated. # dead weakrefs are undefined, so we filter them out @thread_object_registry = grep { defined && do { $_->_reindex_keys; 1 } } @thread_object_registry; $count = 0; # we just cleaned up } sub _reindex_keys { my ( $self, $extra_keys ) = @_; # rehash all the ref keys based on their new StrVal %{ $self->[0] } = map { refaddr($_->[0]) => $_ } (values(%{ $self->[0] }), @{ $extra_keys || [] }); } sub FETCH { my($s, $k) = @_; if (ref $k) { my $kstr = refaddr($k); if (defined $s->[0]{$kstr}) { $s->[0]{$kstr}[1]; } else { undef; } } else { $s->[1]{$k}; } } sub STORE { my($s, $k, $v) = @_; if (ref $k) { $s->[0]{refaddr($k)} = [$k, $v]; } else { $s->[1]{$k} = $v; } $v; } sub DELETE { my($s, $k) = @_; (ref $k) ? (delete($s->[0]{refaddr($k)}) || [])->[1] : delete($s->[1]{$k}); } sub EXISTS { my($s, $k) = @_; (ref $k) ? exists($s->[0]{refaddr($k)}) : exists($s->[1]{$k}); } sub FIRSTKEY { my $s = shift; keys %{$s->[0]}; # reset iterator keys %{$s->[1]}; # reset iterator $s->[2] = 0; # flag for iteration, see NEXTKEY $s->NEXTKEY; } sub NEXTKEY { my $s = shift; my ($k, $v); if (!$s->[2]) { if (($k, $v) = each %{$s->[0]}) { return $v->[0]; } else { $s->[2] = 1; } } return each %{$s->[1]}; } sub CLEAR { my $s = shift; $s->[2] = 0; %{$s->[0]} = (); %{$s->[1]} = (); } package Tie::RefHash::Nestable; use vars '@ISA'; @ISA = 'Tie::RefHash'; sub STORE { my($s, $k, $v) = @_; if (ref($v) eq 'HASH' and not tied %$v) { my @elems = %$v; tie %$v, ref($s), @elems; } $s->SUPER::STORE($k, $v); } 1; package Tie::Scalar; our $VERSION = '1.04'; use Carp; use warnings::register; sub new { my $pkg = shift; $pkg->TIESCALAR(@_); } # "Grandfather" the new, a la Tie::Hash sub TIESCALAR { my $pkg = shift; my $pkg_new = $pkg -> can ('new'); if ($pkg_new and $pkg ne __PACKAGE__) { my $my_new = __PACKAGE__ -> can ('new'); if ($pkg_new == $my_new) { # # Prevent recursion # croak "$pkg must define either a TIESCALAR() or a new() method"; } warnings::warnif ("WARNING: calling ${pkg}->new since " . "${pkg}->TIESCALAR is missing"); $pkg -> new (@_); } else { croak "$pkg doesn't define a TIESCALAR method"; } } sub FETCH { my $pkg = ref $_[0]; croak "$pkg doesn't define a FETCH method"; } sub STORE { my $pkg = ref $_[0]; croak "$pkg doesn't define a STORE method"; } # # The Tie::StdScalar package provides scalars that behave exactly like # Perl's built-in scalars. Good base to inherit from, if you're only going to # tweak a small bit. # package Tie::StdScalar; @ISA = qw(Tie::Scalar); sub TIESCALAR { my $class = shift; my $instance = @_ ? shift : undef; return bless \$instance => $class; } sub FETCH { return ${$_[0]}; } sub STORE { ${$_[0]} = $_[1]; } sub DESTROY { undef ${$_[0]}; } 1; package DBM_Filter::null ; use strict; use warnings; our $VERSION = '0.03'; sub Store { no warnings 'uninitialized'; $_ .= "\x00" ; } sub Fetch { no warnings 'uninitialized'; s/\x00$// ; } 1; __END__ package DBM_Filter::compress ; use strict; use warnings; use Carp; our $VERSION = '0.03'; BEGIN { eval { require Compress::Zlib; Compress::Zlib->import() }; croak "Compress::Zlib module not found.\n" if $@; } sub Store { $_ = compress($_) } sub Fetch { $_ = uncompress($_) } 1; __END__ package DBM_Filter::int32 ; use strict; use warnings; our $VERSION = '0.03'; # todo get Filter to figure endian. sub Store { $_ = 0 if ! defined $_ || $_ eq "" ; $_ = pack("i", $_); } sub Fetch { no warnings 'uninitialized'; $_ = unpack("i", $_); } 1; __END__ package DBM_Filter::utf8 ; use strict; use warnings; use Carp; our $VERSION = '0.03'; BEGIN { eval { require Encode; }; croak "Encode module not found.\n" if $@; } sub Store { $_ = Encode::encode_utf8($_) if defined $_ } sub Fetch { $_ = Encode::decode_utf8($_) if defined $_ } 1; __END__ package DBM_Filter::encode ; use strict; use warnings; use Carp; our $VERSION = '0.03'; BEGIN { eval { require Encode; }; croak "Encode module not found.\n" if $@; } sub Filter { my $encoding_name = shift || "utf8"; my $encoding = Encode::find_encoding($encoding_name) ; croak "Encoding '$encoding_name' is not available" unless $encoding; return { Store => sub { $_ = $encoding->encode($_) if defined $_ ; }, Fetch => sub { $_ = $encoding->decode($_) if defined $_ ; } } ; } 1; __END__ ### the gnu tar specification: ### http://www.gnu.org/software/tar/manual/tar.html ### ### and the pax format spec, which tar derives from: ### http://www.opengroup.org/onlinepubs/007904975/utilities/pax.html package Archive::Tar; require 5.005_03; use Cwd; use IO::Zlib; use IO::File; use Carp qw(carp croak); use File::Spec (); use File::Spec::Unix (); use File::Path (); use Archive::Tar::File; use Archive::Tar::Constant; require Exporter; use strict; use vars qw[$DEBUG $error $VERSION $WARN $FOLLOW_SYMLINK $CHOWN $CHMOD $DO_NOT_USE_PREFIX $HAS_PERLIO $HAS_IO_STRING $SAME_PERMISSIONS $INSECURE_EXTRACT_MODE $ZERO_PAD_NUMBERS @ISA @EXPORT $RESOLVE_SYMLINK ]; @ISA = qw[Exporter]; @EXPORT = qw[ COMPRESS_GZIP COMPRESS_BZIP ]; $DEBUG = 0; $WARN = 1; $FOLLOW_SYMLINK = 0; $VERSION = "2.30"; $CHOWN = 1; $CHMOD = 1; $SAME_PERMISSIONS = $> == 0 ? 1 : 0; $DO_NOT_USE_PREFIX = 0; $INSECURE_EXTRACT_MODE = 0; $ZERO_PAD_NUMBERS = 0; $RESOLVE_SYMLINK = $ENV{'PERL5_AT_RESOLVE_SYMLINK'} || 'speed'; BEGIN { use Config; $HAS_PERLIO = $Config::Config{useperlio}; ### try and load IO::String anyway, so you can dynamically ### switch between perlio and IO::String $HAS_IO_STRING = eval { require IO::String; import IO::String; 1; } || 0; } my $tmpl = { _data => [ ], _file => 'Unknown', }; ### install get/set accessors for this object. for my $key ( keys %$tmpl ) { no strict 'refs'; *{__PACKAGE__."::$key"} = sub { my $self = shift; $self->{$key} = $_[0] if @_; return $self->{$key}; } } sub new { my $class = shift; $class = ref $class if ref $class; ### copying $tmpl here since a shallow copy makes it use the ### same aref, causing for files to remain in memory always. my $obj = bless { _data => [ ], _file => 'Unknown', _error => '' }, $class; if (@_) { unless ( $obj->read( @_ ) ) { $obj->_error(qq[No data could be read from file]); return; } } return $obj; } sub read { my $self = shift; my $file = shift; my $gzip = shift || 0; my $opts = shift || {}; unless( defined $file ) { $self->_error( qq[No file to read from!] ); return; } else { $self->_file( $file ); } my $handle = $self->_get_handle($file, $gzip, READ_ONLY->( ZLIB ) ) or return; my $data = $self->_read_tar( $handle, $opts ) or return; $self->_data( $data ); return wantarray ? @$data : scalar @$data; } sub _get_handle { my $self = shift; my $file = shift; return unless defined $file; my $compress = shift || 0; my $mode = shift || READ_ONLY->( ZLIB ); # default to read only ### Check if file is a file handle or IO glob if ( ref $file ) { return $file if eval{ *$file{IO} }; return $file if eval{ $file->isa(q{IO::Handle}) }; $file = q{}.$file; } ### get a FH opened to the right class, so we can use it transparently ### throughout the program my $fh; { ### reading magic only makes sense if we're opening a file for ### reading. otherwise, just use what the user requested. my $magic = ''; if( MODE_READ->($mode) ) { open my $tmp, $file or do { $self->_error( qq[Could not open '$file' for reading: $!] ); return; }; ### read the first 4 bites of the file to figure out which class to ### use to open the file. sysread( $tmp, $magic, 4 ); close $tmp; } ### is it bzip? ### if you asked specifically for bzip compression, or if we're in ### read mode and the magic numbers add up, use bzip if( BZIP and ( ($compress eq COMPRESS_BZIP) or ( MODE_READ->($mode) and $magic =~ BZIP_MAGIC_NUM ) ) ) { ### different reader/writer modules, different error vars... sigh if( MODE_READ->($mode) ) { $fh = IO::Uncompress::Bunzip2->new( $file, MultiStream => 1 ) or do { $self->_error( qq[Could not read '$file': ] . $IO::Uncompress::Bunzip2::Bunzip2Error ); return; }; } else { $fh = IO::Compress::Bzip2->new( $file ) or do { $self->_error( qq[Could not write to '$file': ] . $IO::Compress::Bzip2::Bzip2Error ); return; }; } ### is it gzip? ### if you asked for compression, if you wanted to read or the gzip ### magic number is present (redundant with read) } elsif( ZLIB and ( $compress or MODE_READ->($mode) or $magic =~ GZIP_MAGIC_NUM ) ) { $fh = IO::Zlib->new; unless( $fh->open( $file, $mode ) ) { $self->_error(qq[Could not create filehandle for '$file': $!]); return; } ### is it plain tar? } else { $fh = IO::File->new; unless( $fh->open( $file, $mode ) ) { $self->_error(qq[Could not create filehandle for '$file': $!]); return; } ### enable bin mode on tar archives binmode $fh; } } return $fh; } sub _read_tar { my $self = shift; my $handle = shift or return; my $opts = shift || {}; my $count = $opts->{limit} || 0; my $filter = $opts->{filter}; my $md5 = $opts->{md5} || 0; # cdrake my $filter_cb = $opts->{filter_cb}; my $extract = $opts->{extract} || 0; ### set a cap on the amount of files to extract ### my $limit = 0; $limit = 1 if $count > 0; my $tarfile = [ ]; my $chunk; my $read = 0; my $real_name; # to set the name of a file when # we're encountering @longlink my $data; LOOP: while( $handle->read( $chunk, HEAD ) ) { ### IO::Zlib doesn't support this yet my $offset; if ( ref($handle) ne 'IO::Zlib' ) { local $@; $offset = eval { tell $handle } || 'unknown'; $@ = ''; } else { $offset = 'unknown'; } unless( $read++ ) { my $gzip = GZIP_MAGIC_NUM; if( $chunk =~ /$gzip/ ) { $self->_error( qq[Cannot read compressed format in tar-mode] ); return; } ### size is < HEAD, which means a corrupted file, as the minimum ### length is _at least_ HEAD if (length $chunk != HEAD) { $self->_error( qq[Cannot read enough bytes from the tarfile] ); return; } } ### if we can't read in all bytes... ### last if length $chunk != HEAD; ### Apparently this should really be two blocks of 512 zeroes, ### but GNU tar sometimes gets it wrong. See comment in the ### source code (tar.c) to GNU cpio. next if $chunk eq TAR_END; ### according to the posix spec, the last 12 bytes of the header are ### null bytes, to pad it to a 512 byte block. That means if these ### bytes are NOT null bytes, it's a corrupt header. See: ### www.koders.com/c/fidCE473AD3D9F835D690259D60AD5654591D91D5BA.aspx ### line 111 { my $nulls = join '', "\0" x 12; unless( $nulls eq substr( $chunk, 500, 12 ) ) { $self->_error( qq[Invalid header block at offset $offset] ); next LOOP; } } ### pass the realname, so we can set it 'proper' right away ### some of the heuristics are done on the name, so important ### to set it ASAP my $entry; { my %extra_args = (); $extra_args{'name'} = $$real_name if defined $real_name; unless( $entry = Archive::Tar::File->new( chunk => $chunk, %extra_args ) ) { $self->_error( qq[Couldn't read chunk at offset $offset] ); next LOOP; } } ### ignore labels: ### http://www.gnu.org/software/tar/manual/html_chapter/Media.html#SEC159 next if $entry->is_label; if( length $entry->type and ($entry->is_file || $entry->is_longlink) ) { if ( $entry->is_file && !$entry->validate ) { ### sometimes the chunk is rather fux0r3d and a whole 512 ### bytes ends up in the ->name area. ### clean it up, if need be my $name = $entry->name; $name = substr($name, 0, 100) if length $name > 100; $name =~ s/\n/ /g; $self->_error( $name . qq[: checksum error] ); next LOOP; } my $block = BLOCK_SIZE->( $entry->size ); $data = $entry->get_content_by_ref; my $skip = 0; my $ctx; # cdrake ### skip this entry if we're filtering if($md5) { # cdrake $ctx = Digest::MD5->new; # cdrake $skip=5; # cdrake } elsif ($filter && $entry->name !~ $filter) { $skip = 1; } elsif ($filter_cb && ! $filter_cb->($entry)) { $skip = 2; ### skip this entry if it's a pax header. This is a special file added ### by, among others, git-generated tarballs. It holds comments and is ### not meant for extracting. See #38932: pax_global_header extracted } elsif ( $entry->name eq PAX_HEADER or $entry->type =~ /^(x|g)$/ ) { $skip = 3; } if ($skip) { # # Since we're skipping, do not allocate memory for the # whole file. Read it 64 BLOCKS at a time. Do not # complete the skip yet because maybe what we read is a # longlink and it won't get skipped after all # my $amt = $block; my $fsz=$entry->size; # cdrake while ($amt > 0) { $$data = ''; my $this = 64 * BLOCK; $this = $amt if $this > $amt; if( $handle->read( $$data, $this ) < $this ) { $self->_error( qq[Read error on tarfile (missing data) ']. $entry->full_path ."' at offset $offset" ); next LOOP; } $amt -= $this; $fsz -= $this; # cdrake substr ($$data, $fsz) = "" if ($fsz<0); # remove external junk prior to md5 # cdrake $ctx->add($$data) if($skip==5); # cdrake } $$data = $ctx->hexdigest if($skip==5 && !$entry->is_longlink && !$entry->is_unknown && !$entry->is_label ) ; # cdrake } else { ### just read everything into memory ### can't do lazy loading since IO::Zlib doesn't support 'seek' ### this is because Compress::Zlib doesn't support it =/ ### this reads in the whole data in one read() call. if ( $handle->read( $$data, $block ) < $block ) { $self->_error( qq[Read error on tarfile (missing data) ']. $entry->full_path ."' at offset $offset" ); next LOOP; } ### throw away trailing garbage ### substr ($$data, $entry->size) = "" if defined $$data; } ### part II of the @LongLink munging -- need to do /after/ ### the checksum check. if( $entry->is_longlink ) { ### weird thing in tarfiles -- if the file is actually a ### @LongLink, the data part seems to have a trailing ^@ ### (unprintable) char. to display, pipe output through less. ### but that doesn't *always* happen.. so check if the last ### character is a control character, and if so remove it ### at any rate, we better remove that character here, or tests ### like 'eq' and hash lookups based on names will SO not work ### remove it by calculating the proper size, and then ### tossing out everything that's longer than that size. ### count number of nulls my $nulls = $$data =~ tr/\0/\0/; ### cut data + size by that many bytes $entry->size( $entry->size - $nulls ); substr ($$data, $entry->size) = ""; } } ### clean up of the entries.. posix tar /apparently/ has some ### weird 'feature' that allows for filenames > 255 characters ### they'll put a header in with as name '././@LongLink' and the ### contents will be the name of the /next/ file in the archive ### pretty crappy and kludgy if you ask me ### set the name for the next entry if this is a @LongLink; ### this is one ugly hack =/ but needed for direct extraction if( $entry->is_longlink ) { $real_name = $data; next LOOP; } elsif ( defined $real_name ) { $entry->name( $$real_name ); $entry->prefix(''); undef $real_name; } if ($filter && $entry->name !~ $filter) { next LOOP; } elsif ($filter_cb && ! $filter_cb->($entry)) { next LOOP; ### skip this entry if it's a pax header. This is a special file added ### by, among others, git-generated tarballs. It holds comments and is ### not meant for extracting. See #38932: pax_global_header extracted } elsif ( $entry->name eq PAX_HEADER or $entry->type =~ /^(x|g)$/ ) { next LOOP; } if ( $extract && !$entry->is_longlink && !$entry->is_unknown && !$entry->is_label ) { $self->_extract_file( $entry ) or return; } ### Guard against tarfiles with garbage at the end last LOOP if $entry->name eq ''; ### push only the name on the rv if we're extracting ### -- for extract_archive push @$tarfile, ($extract ? $entry->name : $entry); if( $limit ) { $count-- unless $entry->is_longlink || $entry->is_dir; last LOOP unless $count; } } continue { undef $data; } return $tarfile; } sub contains_file { my $self = shift; my $full = shift; return unless defined $full; ### don't warn if the entry isn't there.. that's what this function ### is for after all. local $WARN = 0; return 1 if $self->_find_entry($full); return; } sub extract { my $self = shift; my @args = @_; my @files; my $hashmap; # use the speed optimization for all extracted files local($self->{cwd}) = cwd() unless $self->{cwd}; ### you requested the extraction of only certain files if( @args ) { for my $file ( @args ) { ### it's already an object? if( UNIVERSAL::isa( $file, 'Archive::Tar::File' ) ) { push @files, $file; next; ### go find it then } else { # create hash-map once to speed up lookup $hashmap = $hashmap || { map { $_->full_path, $_ } @{$self->_data} }; if (exists $hashmap->{$file}) { ### we found the file you're looking for push @files, $hashmap->{$file}; } else { return $self->_error( qq[Could not find '$file' in archive] ); } } } ### just grab all the file items } else { @files = $self->get_files; } ### nothing found? that's an error unless( scalar @files ) { $self->_error( qq[No files found for ] . $self->_file ); return; } ### now extract them for my $entry ( @files ) { unless( $self->_extract_file( $entry ) ) { $self->_error(q[Could not extract ']. $entry->full_path .q['] ); return; } } return @files; } sub extract_file { my $self = shift; my $file = shift; return unless defined $file; my $alt = shift; my $entry = $self->_find_entry( $file ) or $self->_error( qq[Could not find an entry for '$file'] ), return; return $self->_extract_file( $entry, $alt ); } sub _extract_file { my $self = shift; my $entry = shift or return; my $alt = shift; ### you wanted an alternate extraction location ### my $name = defined $alt ? $alt : $entry->full_path; ### splitpath takes a bool at the end to indicate ### that it's splitting a dir my ($vol,$dirs,$file); if ( defined $alt ) { # It's a local-OS path ($vol,$dirs,$file) = File::Spec->splitpath( $alt, $entry->is_dir ); } else { ($vol,$dirs,$file) = File::Spec::Unix->splitpath( $name, $entry->is_dir ); } my $dir; ### is $name an absolute path? ### if( $vol || File::Spec->file_name_is_absolute( $dirs ) ) { ### absolute names are not allowed to be in tarballs under ### strict mode, so only allow it if a user tells us to do it if( not defined $alt and not $INSECURE_EXTRACT_MODE ) { $self->_error( q[Entry ']. $entry->full_path .q[' is an absolute path. ]. q[Not extracting absolute paths under SECURE EXTRACT MODE] ); return; } ### user asked us to, it's fine. $dir = File::Spec->catpath( $vol, $dirs, "" ); ### it's a relative path ### } else { my $cwd = (ref $self and defined $self->{cwd}) ? $self->{cwd} : cwd(); my @dirs = defined $alt ? File::Spec->splitdir( $dirs ) # It's a local-OS path : File::Spec::Unix->splitdir( $dirs ); # it's UNIX-style, likely # straight from the tarball if( not defined $alt and not $INSECURE_EXTRACT_MODE ) { ### paths that leave the current directory are not allowed under ### strict mode, so only allow it if a user tells us to do this. if( grep { $_ eq '..' } @dirs ) { $self->_error( q[Entry ']. $entry->full_path .q[' is attempting to leave ]. q[the current working directory. Not extracting under ]. q[SECURE EXTRACT MODE] ); return; } ### the archive may be asking us to extract into a symlink. This ### is not sane and a possible security issue, as outlined here: ### https://rt.cpan.org/Ticket/Display.html?id=30380 ### https://bugzilla.redhat.com/show_bug.cgi?id=295021 ### https://issues.rpath.com/browse/RPL-1716 my $full_path = $cwd; for my $d ( @dirs ) { $full_path = File::Spec->catdir( $full_path, $d ); ### we've already checked this one, and it's safe. Move on. next if ref $self and $self->{_link_cache}->{$full_path}; if( -l $full_path ) { my $to = readlink $full_path; my $diag = "symlinked directory ($full_path => $to)"; $self->_error( q[Entry ']. $entry->full_path .q[' is attempting to ]. qq[extract to a $diag. This is considered a security ]. q[vulnerability and not allowed under SECURE EXTRACT ]. q[MODE] ); return; } ### XXX keep a cache if possible, so the stats become cheaper: $self->{_link_cache}->{$full_path} = 1 if ref $self; } } ### '.' is the directory delimiter on VMS, which has to be escaped ### or changed to '_' on vms. vmsify is used, because older versions ### of vmspath do not handle this properly. ### Must not add a '/' to an empty directory though. map { length() ? VMS::Filespec::vmsify($_.'/') : $_ } @dirs if ON_VMS; my ($cwd_vol,$cwd_dir,$cwd_file) = File::Spec->splitpath( $cwd ); my @cwd = File::Spec->splitdir( $cwd_dir ); push @cwd, $cwd_file if length $cwd_file; ### We need to pass '' as the last element to catpath. Craig Berry ### explains why (msgid ): ### The root problem is that splitpath on UNIX always returns the ### final path element as a file even if it is a directory, and of ### course there is no way it can know the difference without checking ### against the filesystem, which it is documented as not doing. When ### you turn around and call catpath, on VMS you have to know which bits ### are directory bits and which bits are file bits. In this case we ### know the result should be a directory. I had thought you could omit ### the file argument to catpath in such a case, but apparently on UNIX ### you can't. $dir = File::Spec->catpath( $cwd_vol, File::Spec->catdir( @cwd, @dirs ), '' ); ### catdir() returns undef if the path is longer than 255 chars on ### older VMS systems. unless ( defined $dir ) { $^W && $self->_error( qq[Could not compose a path for '$dirs'\n] ); return; } } if( -e $dir && !-d _ ) { $^W && $self->_error( qq['$dir' exists, but it's not a directory!\n] ); return; } unless ( -d _ ) { eval { File::Path::mkpath( $dir, 0, 0777 ) }; if( $@ ) { my $fp = $entry->full_path; $self->_error(qq[Could not create directory '$dir' for '$fp': $@]); return; } ### XXX chown here? that might not be the same as in the archive ### as we're only chown'ing to the owner of the file we're extracting ### not to the owner of the directory itself, which may or may not ### be another entry in the archive ### Answer: no, gnu tar doesn't do it either, it'd be the wrong ### way to go. #if( $CHOWN && CAN_CHOWN ) { # chown $entry->uid, $entry->gid, $dir or # $self->_error( qq[Could not set uid/gid on '$dir'] ); #} } ### we're done if we just needed to create a dir ### return 1 if $entry->is_dir; my $full = File::Spec->catfile( $dir, $file ); if( $entry->is_unknown ) { $self->_error( qq[Unknown file type for file '$full'] ); return; } ### If a file system already contains a block device with the same name as ### the being extracted regular file, we would write the file's content ### to the block device. So remove the existing file (block device) now. ### If an archive contains multiple same-named entries, the last one ### should replace the previous ones. So remove the old file now. ### If the old entry is a symlink to a file outside of the CWD, the new ### entry would create a file there. This is CVE-2018-12015 ### . if (-l $full || -e _) { if (!unlink $full) { $self->_error( qq[Could not remove old file '$full': $!] ); return; } } if( length $entry->type && $entry->is_file ) { my $fh = IO::File->new; $fh->open( $full, '>' ) or ( $self->_error( qq[Could not open file '$full': $!] ), return ); if( $entry->size ) { binmode $fh; syswrite $fh, $entry->data or ( $self->_error( qq[Could not write data to '$full'] ), return ); } close $fh or ( $self->_error( qq[Could not close file '$full'] ), return ); } else { $self->_make_special_file( $entry, $full ) or return; } ### only update the timestamp if it's not a symlink; that will change the ### timestamp of the original. This addresses bug #33669: Could not update ### timestamp warning on symlinks if( not -l $full ) { utime time, $entry->mtime - TIME_OFFSET, $full or $self->_error( qq[Could not update timestamp] ); } if( $CHOWN && CAN_CHOWN->() and not -l $full ) { chown $entry->uid, $entry->gid, $full or $self->_error( qq[Could not set uid/gid on '$full'] ); } ### only chmod if we're allowed to, but never chmod symlinks, since they'll ### change the perms on the file they're linking too... if( $CHMOD and not -l $full ) { my $mode = $entry->mode; unless ($SAME_PERMISSIONS) { $mode &= ~(oct(7000) | umask); } chmod $mode, $full or $self->_error( qq[Could not chown '$full' to ] . $entry->mode ); } return 1; } sub _make_special_file { my $self = shift; my $entry = shift or return; my $file = shift; return unless defined $file; my $err; if( $entry->is_symlink ) { my $fail; if( ON_UNIX ) { symlink( $entry->linkname, $file ) or $fail++; } else { $self->_extract_special_file_as_plain_file( $entry, $file ) or $fail++; } $err = qq[Making symbolic link '$file' to '] . $entry->linkname .q[' failed] if $fail; } elsif ( $entry->is_hardlink ) { my $fail; if( ON_UNIX ) { link( $entry->linkname, $file ) or $fail++; } else { $self->_extract_special_file_as_plain_file( $entry, $file ) or $fail++; } $err = qq[Making hard link from '] . $entry->linkname . qq[' to '$file' failed] if $fail; } elsif ( $entry->is_fifo ) { ON_UNIX && !system('mknod', $file, 'p') or $err = qq[Making fifo ']. $entry->name .qq[' failed]; } elsif ( $entry->is_blockdev or $entry->is_chardev ) { my $mode = $entry->is_blockdev ? 'b' : 'c'; ON_UNIX && !system('mknod', $file, $mode, $entry->devmajor, $entry->devminor) or $err = qq[Making block device ']. $entry->name .qq[' (maj=] . $entry->devmajor . qq[ min=] . $entry->devminor . qq[) failed.]; } elsif ( $entry->is_socket ) { ### the original doesn't do anything special for sockets.... ### 1; } return $err ? $self->_error( $err ) : 1; } ### don't know how to make symlinks, let's just extract the file as ### a plain file sub _extract_special_file_as_plain_file { my $self = shift; my $entry = shift or return; my $file = shift; return unless defined $file; my $err; TRY: { my $orig = $self->_find_entry( $entry->linkname, $entry ); unless( $orig ) { $err = qq[Could not find file '] . $entry->linkname . qq[' in memory.]; last TRY; } ### clone the entry, make it appear as a normal file ### my $clone = $orig->clone; $clone->_downgrade_to_plainfile; $self->_extract_file( $clone, $file ) or last TRY; return 1; } return $self->_error($err); } sub list_files { my $self = shift; my $aref = shift || [ ]; unless( $self->_data ) { $self->read() or return; } if( @$aref == 0 or ( @$aref == 1 and $aref->[0] eq 'name' ) ) { return map { $_->full_path } @{$self->_data}; } else { #my @rv; #for my $obj ( @{$self->_data} ) { # push @rv, { map { $_ => $obj->$_() } @$aref }; #} #return @rv; ### this does the same as the above.. just needs a +{ } ### to make sure perl doesn't confuse it for a block return map { my $o=$_; +{ map { $_ => $o->$_() } @$aref } } @{$self->_data}; } } sub _find_entry { my $self = shift; my $file = shift; unless( defined $file ) { $self->_error( qq[No file specified] ); return; } ### it's an object already return $file if UNIVERSAL::isa( $file, 'Archive::Tar::File' ); seach_entry: if($self->_data){ for my $entry ( @{$self->_data} ) { my $path = $entry->full_path; return $entry if $path eq $file; } } if($Archive::Tar::RESOLVE_SYMLINK!~/none/){ if(my $link_entry = shift()){#fallback mode when symlinks are using relative notations ( ../a/./b/text.bin ) $file = _symlinks_resolver( $link_entry->name, $file ); goto seach_entry if $self->_data; #this will be slower than never, but won't failed! my $iterargs = $link_entry->{'_archive'}; if($Archive::Tar::RESOLVE_SYMLINK=~/speed/ && @$iterargs==3){ #faster but whole archive will be read in memory #read whole archive and share data my $archive = Archive::Tar->new; $archive->read( @$iterargs ); push @$iterargs, $archive; #take a trace for destruction if($archive->_data){ $self->_data( $archive->_data ); goto seach_entry; } }#faster {#slower but lower memory usage # $iterargs = [$filename, $compressed, $opts]; my $next = Archive::Tar->iter( @$iterargs ); while(my $e = $next->()){ if($e->full_path eq $file){ undef $next; return $e; } } }#slower } } $self->_error( qq[No such file in archive: '$file'] ); return; } sub get_files { my $self = shift; return @{ $self->_data } unless @_; my @list; for my $file ( @_ ) { push @list, grep { defined } $self->_find_entry( $file ); } return @list; } sub get_content { my $self = shift; my $entry = $self->_find_entry( shift ) or return; return $entry->data; } sub replace_content { my $self = shift; my $entry = $self->_find_entry( shift ) or return; return $entry->replace_content( shift ); } sub rename { my $self = shift; my $file = shift; return unless defined $file; my $new = shift; return unless defined $new; my $entry = $self->_find_entry( $file ) or return; return $entry->rename( $new ); } sub chmod { my $self = shift; my $file = shift; return unless defined $file; my $mode = shift; return unless defined $mode && $mode =~ /^[0-7]{1,4}$/; my @args = ("$mode"); my $entry = $self->_find_entry( $file ) or return; my $x = $entry->chmod( @args ); return $x; } sub chown { my $self = shift; my $file = shift; return unless defined $file; my $uname = shift; return unless defined $uname; my @args = ($uname); push(@args, shift); my $entry = $self->_find_entry( $file ) or return; my $x = $entry->chown( @args ); return $x; } sub remove { my $self = shift; my @list = @_; my %seen = map { $_->full_path => $_ } @{$self->_data}; delete $seen{ $_ } for @list; $self->_data( [values %seen] ); return values %seen; } sub clear { my $self = shift or return; $self->_data( [] ); $self->_file( '' ); return 1; } sub write { my $self = shift; my $file = shift; $file = '' unless defined $file; my $gzip = shift || 0; my $ext_prefix = shift; $ext_prefix = '' unless defined $ext_prefix; my $dummy = ''; ### only need a handle if we have a file to print to ### my $handle = length($file) ? ( $self->_get_handle($file, $gzip, WRITE_ONLY->($gzip) ) or return ) : $HAS_PERLIO ? do { open my $h, '>', \$dummy; $h } : $HAS_IO_STRING ? IO::String->new : __PACKAGE__->no_string_support(); ### Addresses: #41798: Nonempty $\ when writing a TAR file produces a ### corrupt TAR file. Must clear out $\ to make sure no garbage is ### printed to the archive local $\; for my $entry ( @{$self->_data} ) { ### entries to be written to the tarfile ### my @write_me; ### only now will we change the object to reflect the current state ### of the name and prefix fields -- this needs to be limited to ### write() only! my $clone = $entry->clone; ### so, if you don't want use to use the prefix, we'll stuff ### everything in the name field instead if( $DO_NOT_USE_PREFIX ) { ### you might have an extended prefix, if so, set it in the clone ### XXX is ::Unix right? $clone->name( length $ext_prefix ? File::Spec::Unix->catdir( $ext_prefix, $clone->full_path) : $clone->full_path ); $clone->prefix( '' ); ### otherwise, we'll have to set it properly -- prefix part in the ### prefix and name part in the name field. } else { ### split them here, not before! my ($prefix,$name) = $clone->_prefix_and_file( $clone->full_path ); ### you might have an extended prefix, if so, set it in the clone ### XXX is ::Unix right? $prefix = File::Spec::Unix->catdir( $ext_prefix, $prefix ) if length $ext_prefix; $clone->prefix( $prefix ); $clone->name( $name ); } ### names are too long, and will get truncated if we don't add a ### '@LongLink' file... my $make_longlink = ( length($clone->name) > NAME_LENGTH or length($clone->prefix) > PREFIX_LENGTH ) || 0; ### perhaps we need to make a longlink file? if( $make_longlink ) { my $longlink = Archive::Tar::File->new( data => LONGLINK_NAME, $clone->full_path, { type => LONGLINK } ); unless( $longlink ) { $self->_error( qq[Could not create 'LongLink' entry for ] . qq[oversize file '] . $clone->full_path ."'" ); return; }; push @write_me, $longlink; } push @write_me, $clone; ### write the one, optionally 2 a::t::file objects to the handle for my $clone (@write_me) { ### if the file is a symlink, there are 2 options: ### either we leave the symlink intact, but then we don't write any ### data OR we follow the symlink, which means we actually make a ### copy. if we do the latter, we have to change the TYPE of the ### clone to 'FILE' my $link_ok = $clone->is_symlink && $Archive::Tar::FOLLOW_SYMLINK; my $data_ok = !$clone->is_symlink && $clone->has_content; ### downgrade to a 'normal' file if it's a symlink we're going to ### treat as a regular file $clone->_downgrade_to_plainfile if $link_ok; ### get the header for this block my $header = $self->_format_tar_entry( $clone ); unless( $header ) { $self->_error(q[Could not format header for: ] . $clone->full_path ); return; } unless( print $handle $header ) { $self->_error(q[Could not write header for: ] . $clone->full_path); return; } if( $link_ok or $data_ok ) { unless( print $handle $clone->data ) { $self->_error(q[Could not write data for: ] . $clone->full_path); return; } ### pad the end of the clone if required ### print $handle TAR_PAD->( $clone->size ) if $clone->size % BLOCK } } ### done writing these entries } ### write the end markers ### print $handle TAR_END x 2 or return $self->_error( qq[Could not write tar end markers] ); ### did you want it written to a file, or returned as a string? ### my $rv = length($file) ? 1 : $HAS_PERLIO ? $dummy : do { seek $handle, 0, 0; local $/; <$handle> }; ### make sure to close the handle if we created it if ( $file ne $handle ) { unless( close $handle ) { $self->_error( qq[Could not write tar] ); return; } } return $rv; } sub _format_tar_entry { my $self = shift; my $entry = shift or return; my $ext_prefix = shift; $ext_prefix = '' unless defined $ext_prefix; my $no_prefix = shift || 0; my $file = $entry->name; my $prefix = $entry->prefix; $prefix = '' unless defined $prefix; ### remove the prefix from the file name ### not sure if this is still needed --kane ### no it's not -- Archive::Tar::File->_new_from_file will take care of ### this for us. Even worse, this would break if we tried to add a file ### like x/x. #if( length $prefix ) { # $file =~ s/^$match//; #} $prefix = File::Spec::Unix->catdir($ext_prefix, $prefix) if length $ext_prefix; ### not sure why this is... ### my $l = PREFIX_LENGTH; # is ambiguous otherwise... substr ($prefix, 0, -$l) = "" if length $prefix >= PREFIX_LENGTH; my $f1 = "%06o"; my $f2 = $ZERO_PAD_NUMBERS ? "%011o" : "%11o"; ### this might be optimizable with a 'changed' flag in the file objects ### my $tar = pack ( PACK, $file, (map { sprintf( $f1, $entry->$_() ) } qw[mode uid gid]), (map { sprintf( $f2, $entry->$_() ) } qw[size mtime]), "", # checksum field - space padded a bit down (map { $entry->$_() } qw[type linkname magic]), $entry->version || TAR_VERSION, (map { $entry->$_() } qw[uname gname]), (map { sprintf( $f1, $entry->$_() ) } qw[devmajor devminor]), ($no_prefix ? '' : $prefix) ); ### add the checksum ### my $checksum_fmt = $ZERO_PAD_NUMBERS ? "%06o\0" : "%06o\0"; substr($tar,148,7) = sprintf("%6o\0", unpack("%16C*",$tar)); return $tar; } sub add_files { my $self = shift; my @files = @_ or return; my @rv; for my $file ( @files ) { ### you passed an Archive::Tar::File object ### clone it so we don't accidentally have a reference to ### an object from another archive if( UNIVERSAL::isa( $file,'Archive::Tar::File' ) ) { push @rv, $file->clone; next; } eval { if( utf8::is_utf8( $file )) { utf8::encode( $file ); } }; unless( -e $file || -l $file ) { $self->_error( qq[No such file: '$file'] ); next; } my $obj = Archive::Tar::File->new( file => $file ); unless( $obj ) { $self->_error( qq[Unable to add file: '$file'] ); next; } push @rv, $obj; } push @{$self->{_data}}, @rv; return @rv; } sub add_data { my $self = shift; my ($file, $data, $opt) = @_; my $obj = Archive::Tar::File->new( data => $file, $data, $opt ); unless( $obj ) { $self->_error( qq[Unable to add file: '$file'] ); return; } push @{$self->{_data}}, $obj; return $obj; } { $error = ''; my $longmess; sub _error { my $self = shift; my $msg = $error = shift; $longmess = Carp::longmess($error); if (ref $self) { $self->{_error} = $error; $self->{_longmess} = $longmess; } ### set Archive::Tar::WARN to 0 to disable printing ### of errors if( $WARN ) { carp $DEBUG ? $longmess : $msg; } return; } sub error { my $self = shift; if (ref $self) { return shift() ? $self->{_longmess} : $self->{_error}; } else { return shift() ? $longmess : $error; } } } sub setcwd { my $self = shift; my $cwd = shift; $self->{cwd} = $cwd; } sub create_archive { my $class = shift; my $file = shift; return unless defined $file; my $gzip = shift || 0; my @files = @_; unless( @files ) { return $class->_error( qq[Cowardly refusing to create empty archive!] ); } my $tar = $class->new; $tar->add_files( @files ); return $tar->write( $file, $gzip ); } sub iter { my $class = shift; my $filename = shift; return unless defined $filename; my $compressed = shift || 0; my $opts = shift || {}; ### get a handle to read from. my $handle = $class->_get_handle( $filename, $compressed, READ_ONLY->( ZLIB ) ) or return; my @data; my $CONSTRUCT_ARGS = [ $filename, $compressed, $opts ]; return sub { return shift(@data) if @data; # more than one file returned? return unless $handle; # handle exhausted? ### read data, should only return file my $tarfile = $class->_read_tar($handle, { %$opts, limit => 1 }); @data = @$tarfile if ref $tarfile && ref $tarfile eq 'ARRAY'; if($Archive::Tar::RESOLVE_SYMLINK!~/none/){ foreach(@data){ #may refine this heuristic for ON_UNIX? if($_->linkname){ #is there a better slot to store/share it ? $_->{'_archive'} = $CONSTRUCT_ARGS; } } } ### return one piece of data return shift(@data) if @data; ### data is exhausted, free the filehandle undef $handle; if(@$CONSTRUCT_ARGS == 4){ #free archive in memory undef $CONSTRUCT_ARGS->[-1]; } return; }; } sub list_archive { my $class = shift; my $file = shift; return unless defined $file; my $gzip = shift || 0; my $tar = $class->new($file, $gzip); return unless $tar; return $tar->list_files( @_ ); } sub extract_archive { my $class = shift; my $file = shift; return unless defined $file; my $gzip = shift || 0; my $tar = $class->new( ) or return; return $tar->read( $file, $gzip, { extract => 1 } ); } sub has_io_string { return $HAS_IO_STRING; } sub has_perlio { return $HAS_PERLIO; } sub has_zlib_support { return ZLIB } sub has_bzip2_support { return BZIP } sub can_handle_compressed_files { return ZLIB && BZIP ? 1 : 0 } sub no_string_support { croak("You have to install IO::String to support writing archives to strings"); } sub _symlinks_resolver{ my ($src, $trg) = @_; my @src = split /[\/\\]/, $src; my @trg = split /[\/\\]/, $trg; pop @src; #strip out current object name if(@trg and $trg[0] eq ''){ shift @trg; #restart path from scratch @src = ( ); } foreach my $part ( @trg ){ next if $part eq '.'; #ignore current if($part eq '..'){ #got to parent pop @src; } else{ #append it push @src, $part; } } my $path = join('/', @src); warn "_symlinks_resolver('$src','$trg') = $path" if $DEBUG; return $path; } 1; __END__ package Archive::Tar::Constant; BEGIN { require Exporter; $VERSION = '2.30'; @ISA = qw[Exporter]; require Time::Local if $^O eq "MacOS"; } @EXPORT = Archive::Tar::Constant->_list_consts( __PACKAGE__ ); use strict; use warnings; use constant FILE => 0; use constant HARDLINK => 1; use constant SYMLINK => 2; use constant CHARDEV => 3; use constant BLOCKDEV => 4; use constant DIR => 5; use constant FIFO => 6; use constant SOCKET => 8; use constant UNKNOWN => 9; use constant LONGLINK => 'L'; use constant LABEL => 'V'; use constant BUFFER => 4096; use constant HEAD => 512; use constant BLOCK => 512; use constant COMPRESS_GZIP => 9; use constant COMPRESS_BZIP => 'bzip2'; use constant BLOCK_SIZE => sub { my $n = int($_[0]/BLOCK); $n++ if $_[0] % BLOCK; $n * BLOCK }; use constant TAR_PAD => sub { my $x = shift || return; return "\0" x (BLOCK - ($x % BLOCK) ) }; use constant TAR_END => "\0" x BLOCK; use constant READ_ONLY => sub { shift() ? 'rb' : 'r' }; use constant WRITE_ONLY => sub { $_[0] ? 'wb' . shift : 'w' }; use constant MODE_READ => sub { $_[0] =~ /^r/ ? 1 : 0 }; # Pointless assignment to make -w shut up my $getpwuid; $getpwuid = 'unknown' unless eval { my $f = getpwuid (0); }; my $getgrgid; $getgrgid = 'unknown' unless eval { my $f = getgrgid (0); }; use constant UNAME => sub { $getpwuid || scalar getpwuid( shift() ) || '' }; use constant GNAME => sub { $getgrgid || scalar getgrgid( shift() ) || '' }; use constant UID => $>; use constant GID => (split ' ', $) )[0]; use constant MODE => do { 0666 & (0777 & ~umask) }; use constant STRIP_MODE => sub { shift() & 0777 }; use constant CHECK_SUM => " "; use constant UNPACK => 'a100 a8 a8 a8 a12 a12 a8 a1 a100 A6 a2 a32 a32 a8 a8 a155 x12'; # cdrake - size must be a12 - not A12 - or else screws up huge file sizes (>8gb) use constant PACK => 'a100 a8 a8 a8 a12 a12 A8 a1 a100 a6 a2 a32 a32 a8 a8 a155 x12'; use constant NAME_LENGTH => 100; use constant PREFIX_LENGTH => 155; use constant TIME_OFFSET => ($^O eq "MacOS") ? Time::Local::timelocal(0,0,0,1,0,70) : 0; use constant MAGIC => "ustar"; use constant TAR_VERSION => "00"; use constant LONGLINK_NAME => '././@LongLink'; use constant PAX_HEADER => 'pax_global_header'; ### allow ZLIB to be turned off using ENV: DEBUG only use constant ZLIB => do { !$ENV{'PERL5_AT_NO_ZLIB'} and eval { require IO::Zlib }; $ENV{'PERL5_AT_NO_ZLIB'} || $@ ? 0 : 1 }; ### allow BZIP to be turned off using ENV: DEBUG only use constant BZIP => do { !$ENV{'PERL5_AT_NO_BZIP'} and eval { require IO::Uncompress::Bunzip2; require IO::Compress::Bzip2; }; $ENV{'PERL5_AT_NO_BZIP'} || $@ ? 0 : 1 }; use constant GZIP_MAGIC_NUM => qr/^(?:\037\213|\037\235)/; use constant BZIP_MAGIC_NUM => qr/^BZh\d/; use constant CAN_CHOWN => sub { ($> == 0 and $^O ne "MacOS" and $^O ne "MSWin32") }; use constant CAN_READLINK => ($^O ne 'MSWin32' and $^O !~ /RISC(?:[ _])?OS/i and $^O ne 'VMS'); use constant ON_UNIX => ($^O ne 'MSWin32' and $^O ne 'MacOS' and $^O ne 'VMS'); use constant ON_VMS => $^O eq 'VMS'; sub _list_consts { my $class = shift; my $pkg = shift; return unless defined $pkg; # some joker might use '0' as a pkg... my @rv; { no strict 'refs'; my $stash = $pkg . '::'; for my $name (sort keys %$stash ) { ### is it a subentry? my $sub = $pkg->can( $name ); next unless defined $sub; next unless defined prototype($sub) and not length prototype($sub); push @rv, $name; } } return sort @rv; } 1; package Archive::Tar::File; use strict; use Carp (); use IO::File; use File::Spec::Unix (); use File::Spec (); use File::Basename (); ### avoid circular use, so only require; require Archive::Tar; use Archive::Tar::Constant; use vars qw[@ISA $VERSION]; #@ISA = qw[Archive::Tar]; $VERSION = '2.30'; ### set value to 1 to oct() it during the unpack ### my $tmpl = [ name => 0, # string A100 mode => 1, # octal A8 uid => 1, # octal A8 gid => 1, # octal A8 size => 0, # octal # cdrake - not *always* octal.. A12 mtime => 1, # octal A12 chksum => 1, # octal A8 type => 0, # character A1 linkname => 0, # string A100 magic => 0, # string A6 version => 0, # 2 bytes A2 uname => 0, # string A32 gname => 0, # string A32 devmajor => 1, # octal A8 devminor => 1, # octal A8 prefix => 0, # A155 x 12 ### end UNPACK items ### raw => 0, # the raw data chunk data => 0, # the data associated with the file -- # This might be very memory intensive ]; ### install get/set accessors for this object. for ( my $i=0; $i[$i]; no strict 'refs'; *{__PACKAGE__."::$key"} = sub { my $self = shift; $self->{$key} = $_[0] if @_; ### just in case the key is not there or undef or something ### { local $^W = 0; return $self->{$key}; } } } sub new { my $class = shift; my $what = shift; my $obj = ($what eq 'chunk') ? __PACKAGE__->_new_from_chunk( @_ ) : ($what eq 'file' ) ? __PACKAGE__->_new_from_file( @_ ) : ($what eq 'data' ) ? __PACKAGE__->_new_from_data( @_ ) : undef; return $obj; } ### copies the data, creates a clone ### sub clone { my $self = shift; return bless { %$self }, ref $self; } sub _new_from_chunk { my $class = shift; my $chunk = shift or return; # 512 bytes of tar header my %hash = @_; ### filter any arguments on defined-ness of values. ### this allows overriding from what the tar-header is saying ### about this tar-entry. Particularly useful for @LongLink files my %args = map { $_ => $hash{$_} } grep { defined $hash{$_} } keys %hash; ### makes it start at 0 actually... :) ### my $i = -1; my %entry = map { my ($s,$v)=($tmpl->[++$i],$tmpl->[++$i]); # cdrake ($_)=($_=~/^([^\0]*)/) unless($s eq 'size'); # cdrake $s=> $v ? oct $_ : $_ # cdrake # $tmpl->[++$i] => $tmpl->[++$i] ? oct $_ : $_ # removed by cdrake - mucks up binary sizes >8gb } unpack( UNPACK, $chunk ); # cdrake # } map { /^([^\0]*)/ } unpack( UNPACK, $chunk ); # old - replaced now by cdrake if(substr($entry{'size'}, 0, 1) eq "\x80") { # binary size extension for files >8gigs (> octal 77777777777777) # cdrake my @sz=unpack("aCSNN",$entry{'size'}); $entry{'size'}=$sz[4]+(2**32)*$sz[3]+$sz[2]*(2**64); # Use the low 80 bits (should use the upper 15 as well, but as at year 2011, that seems unlikely to ever be needed - the numbers are just too big...) # cdrake } else { # cdrake ($entry{'size'})=($entry{'size'}=~/^([^\0]*)/); $entry{'size'}=oct $entry{'size'}; # cdrake } # cdrake my $obj = bless { %entry, %args }, $class; ### magic is a filetype string.. it should have something like 'ustar' or ### something similar... if the chunk is garbage, skip it return unless $obj->magic !~ /\W/; ### store the original chunk ### $obj->raw( $chunk ); $obj->type(FILE) if ( (!length $obj->type) or ($obj->type =~ /\W/) ); $obj->type(DIR) if ( ($obj->is_file) && ($obj->name =~ m|/$|) ); return $obj; } sub _new_from_file { my $class = shift; my $path = shift; ### path has to at least exist return unless defined $path; my $type = __PACKAGE__->_filetype($path); my $data = ''; READ: { unless ($type == DIR ) { my $fh = IO::File->new; unless( $fh->open($path) ) { ### dangling symlinks are fine, stop reading but continue ### creating the object last READ if $type == SYMLINK; ### otherwise, return from this function -- ### anything that's *not* a symlink should be ### resolvable return; } ### binmode needed to read files properly on win32 ### binmode $fh; $data = do { local $/; <$fh> }; close $fh; } } my @items = qw[mode uid gid size mtime]; my %hash = map { shift(@items), $_ } (lstat $path)[2,4,5,7,9]; if (ON_VMS) { ### VMS has two UID modes, traditional and POSIX. Normally POSIX is ### not used. We currently do not have an easy way to see if we are in ### POSIX mode. In traditional mode, the UID is actually the VMS UIC. ### The VMS UIC has the upper 16 bits is the GID, which in many cases ### the VMS UIC will be larger than 209715, the largest that TAR can ### handle. So for now, assume it is traditional if the UID is larger ### than 0x10000. if ($hash{uid} > 0x10000) { $hash{uid} = $hash{uid} & 0xFFFF; } ### The file length from stat() is the physical length of the file ### However the amount of data read in may be more for some file types. ### Fixed length files are read past the logical EOF to end of the block ### containing. Other file types get expanded on read because record ### delimiters are added. my $data_len = length $data; $hash{size} = $data_len if $hash{size} < $data_len; } ### you *must* set size == 0 on symlinks, or the next entry will be ### though of as the contents of the symlink, which is wrong. ### this fixes bug #7937 $hash{size} = 0 if ($type == DIR or $type == SYMLINK); $hash{mtime} -= TIME_OFFSET; ### strip the high bits off the mode, which we don't need to store $hash{mode} = STRIP_MODE->( $hash{mode} ); ### probably requires some file path munging here ... ### ### name and prefix are set later my $obj = { %hash, name => '', chksum => CHECK_SUM, type => $type, linkname => ($type == SYMLINK and CAN_READLINK) ? readlink $path : '', magic => MAGIC, version => TAR_VERSION, uname => UNAME->( $hash{uid} ), gname => GNAME->( $hash{gid} ), devmajor => 0, # not handled devminor => 0, # not handled prefix => '', data => $data, }; bless $obj, $class; ### fix up the prefix and file from the path my($prefix,$file) = $obj->_prefix_and_file( $path ); $obj->prefix( $prefix ); $obj->name( $file ); return $obj; } sub _new_from_data { my $class = shift; my $path = shift; return unless defined $path; my $data = shift; return unless defined $data; my $opt = shift; my $obj = { data => $data, name => '', mode => MODE, uid => UID, gid => GID, size => length $data, mtime => time - TIME_OFFSET, chksum => CHECK_SUM, type => FILE, linkname => '', magic => MAGIC, version => TAR_VERSION, uname => UNAME->( UID ), gname => GNAME->( GID ), devminor => 0, devmajor => 0, prefix => '', }; ### overwrite with user options, if provided ### if( $opt and ref $opt eq 'HASH' ) { for my $key ( keys %$opt ) { ### don't write bogus options ### next unless exists $obj->{$key}; $obj->{$key} = $opt->{$key}; } } bless $obj, $class; ### fix up the prefix and file from the path my($prefix,$file) = $obj->_prefix_and_file( $path ); $obj->prefix( $prefix ); $obj->name( $file ); return $obj; } sub _prefix_and_file { my $self = shift; my $path = shift; my ($vol, $dirs, $file) = File::Spec->splitpath( $path, $self->is_dir ); my @dirs = File::Spec->splitdir( File::Spec->canonpath($dirs) ); ### if it's a directory, then $file might be empty $file = pop @dirs if $self->is_dir and not length $file; ### splitting ../ gives you the relative path in native syntax map { $_ = '..' if $_ eq '-' } @dirs if ON_VMS; my $prefix = File::Spec::Unix->catdir(@dirs); return( $prefix, $file ); } sub _filetype { my $self = shift; my $file = shift; return unless defined $file; return SYMLINK if (-l $file); # Symlink return FILE if (-f _); # Plain file return DIR if (-d _); # Directory return FIFO if (-p _); # Named pipe return SOCKET if (-S _); # Socket return BLOCKDEV if (-b _); # Block special return CHARDEV if (-c _); # Character special ### shouldn't happen, this is when making archives, not reading ### return LONGLINK if ( $file eq LONGLINK_NAME ); return UNKNOWN; # Something else (like what?) } ### this method 'downgrades' a file to plain file -- this is used for ### symlinks when FOLLOW_SYMLINKS is true. sub _downgrade_to_plainfile { my $entry = shift; $entry->type( FILE ); $entry->mode( MODE ); $entry->linkname(''); return 1; } sub extract { my $self = shift; local $Carp::CarpLevel += 1; return Archive::Tar->_extract_file( $self, @_ ); } sub full_path { my $self = shift; ### if prefix field is empty return $self->name unless defined $self->prefix and length $self->prefix; ### or otherwise, catfile'd return File::Spec::Unix->catfile( $self->prefix, $self->name ); } sub validate { my $self = shift; my $raw = $self->raw; ### don't know why this one is different from the one we /write/ ### substr ($raw, 148, 8) = " "; ### bug #43513: [PATCH] Accept wrong checksums from SunOS and HP-UX tar ### like GNU tar does. See here for details: ### http://www.gnu.org/software/tar/manual/tar.html#SEC139 ### so we do both a signed AND unsigned validate. if one succeeds, that's ### good enough return ( (unpack ("%16C*", $raw) == $self->chksum) or (unpack ("%16c*", $raw) == $self->chksum)) ? 1 : 0; } sub has_content { my $self = shift; return defined $self->data() && length $self->data() ? 1 : 0; } sub get_content { my $self = shift; $self->data( ); } sub get_content_by_ref { my $self = shift; return \$self->{data}; } sub replace_content { my $self = shift; my $data = shift || ''; $self->data( $data ); $self->size( length $data ); return 1; } sub rename { my $self = shift; my $path = shift; return unless defined $path; my ($prefix,$file) = $self->_prefix_and_file( $path ); $self->name( $file ); $self->prefix( $prefix ); return 1; } sub chmod { my $self = shift; my $mode = shift; return unless defined $mode && $mode =~ /^[0-7]{1,4}$/; $self->{mode} = oct($mode); return 1; } sub chown { my $self = shift; my $uname = shift; return unless defined $uname; my $gname; if (-1 != index($uname, ':')) { ($uname, $gname) = split(/:/, $uname); } else { $gname = shift if @_ > 0; } $self->uname( $uname ); $self->gname( $gname ) if $gname; return 1; } #stupid perl5.5.3 needs to warn if it's not numeric sub is_file { local $^W; FILE == $_[0]->type } sub is_dir { local $^W; DIR == $_[0]->type } sub is_hardlink { local $^W; HARDLINK == $_[0]->type } sub is_symlink { local $^W; SYMLINK == $_[0]->type } sub is_chardev { local $^W; CHARDEV == $_[0]->type } sub is_blockdev { local $^W; BLOCKDEV == $_[0]->type } sub is_fifo { local $^W; FIFO == $_[0]->type } sub is_socket { local $^W; SOCKET == $_[0]->type } sub is_unknown { local $^W; UNKNOWN == $_[0]->type } sub is_longlink { local $^W; LONGLINK eq $_[0]->type } sub is_label { local $^W; LABEL eq $_[0]->type } 1; package Config::Extensions; use strict; our (%Extensions, $VERSION, @ISA, @EXPORT_OK); use Config; require Exporter; $VERSION = '0.02'; @ISA = 'Exporter'; @EXPORT_OK = '%Extensions'; foreach my $type (qw(static dynamic nonxs)) { foreach (split /\s+/, $Config{$type . '_ext'}) { s!/!::!g; $Extensions{$_} = $type; } } 1; __END__ #!/pro/bin/perl package Config::Perl::V; use strict; use warnings; use Config; use Exporter; use vars qw($VERSION @ISA @EXPORT_OK %EXPORT_TAGS); $VERSION = "0.29"; @ISA = qw( Exporter ); @EXPORT_OK = qw( plv2hash summary myconfig signature ); %EXPORT_TAGS = ( all => [ @EXPORT_OK ], sig => [ "signature" ], ); # Characteristics of this binary (from libperl): # Compile-time options: DEBUGGING PERL_DONT_CREATE_GVSV PERL_MALLOC_WRAP # USE_64_BIT_INT USE_LARGE_FILES USE_PERLIO # The list are as the perl binary has stored it in PL_bincompat_options # search for it in # perl.c line 1643 S_Internals_V () # perl -ne'(/^S_Internals_V/../^}/)&&s/^\s+"( .*)"/$1/ and print' perl.c # perl.h line 4566 PL_bincompat_options # perl -ne'(/^\w.*PL_bincompat/../^\w}/)&&s/^\s+"( .*)"/$1/ and print' perl.h my %BTD = map { $_ => 0 } qw( DEBUGGING NO_HASH_SEED NO_MATHOMS NO_TAINT_SUPPORT PERL_BOOL_AS_CHAR PERL_COPY_ON_WRITE PERL_DISABLE_PMC PERL_DONT_CREATE_GVSV PERL_EXTERNAL_GLOB PERL_HASH_FUNC_DJB2 PERL_HASH_FUNC_MURMUR3 PERL_HASH_FUNC_ONE_AT_A_TIME PERL_HASH_FUNC_ONE_AT_A_TIME_HARD PERL_HASH_FUNC_ONE_AT_A_TIME_OLD PERL_HASH_FUNC_SDBM PERL_HASH_FUNC_SIPHASH PERL_HASH_FUNC_SUPERFAST PERL_IS_MINIPERL PERL_MALLOC_WRAP PERL_MEM_LOG PERL_MEM_LOG_ENV PERL_MEM_LOG_ENV_FD PERL_MEM_LOG_NOIMPL PERL_MEM_LOG_STDERR PERL_MEM_LOG_TIMESTAMP PERL_NEW_COPY_ON_WRITE PERL_OP_PARENT PERL_PERTURB_KEYS_DETERMINISTIC PERL_PERTURB_KEYS_DISABLED PERL_PERTURB_KEYS_RANDOM PERL_PRESERVE_IVUV PERL_RELOCATABLE_INCPUSH PERL_USE_DEVEL PERL_USE_SAFE_PUTENV SILENT_NO_TAINT_SUPPORT UNLINK_ALL_VERSIONS USE_ATTRIBUTES_FOR_PERLIO USE_FAST_STDIO USE_HASH_SEED_EXPLICIT USE_LOCALE USE_LOCALE_CTYPE USE_NO_REGISTRY USE_PERL_ATOF USE_SITECUSTOMIZE DEBUG_LEAKING_SCALARS DEBUG_LEAKING_SCALARS_FORK_DUMP DECCRTL_SOCKETS FAKE_THREADS FCRYPT HAS_TIMES HAVE_INTERP_INTERN MULTIPLICITY MYMALLOC PERL_DEBUG_READONLY_COW PERL_DEBUG_READONLY_OPS PERL_GLOBAL_STRUCT PERL_GLOBAL_STRUCT_PRIVATE PERL_IMPLICIT_CONTEXT PERL_IMPLICIT_SYS PERLIO_LAYERS PERL_MAD PERL_MICRO PERL_NEED_APPCTX PERL_NEED_TIMESBASE PERL_OLD_COPY_ON_WRITE PERL_POISON PERL_SAWAMPERSAND PERL_TRACK_MEMPOOL PERL_USES_PL_PIDSTATUS PL_OP_SLAB_ALLOC THREADS_HAVE_PIDS USE_64_BIT_ALL USE_64_BIT_INT USE_IEEE USE_ITHREADS USE_LARGE_FILES USE_LOCALE_COLLATE USE_LOCALE_NUMERIC USE_LOCALE_TIME USE_LONG_DOUBLE USE_PERLIO USE_QUADMATH USE_REENTRANT_API USE_SFIO USE_SOCKS VMS_DO_SOCKETS VMS_SHORTEN_LONG_SYMBOLS VMS_SYMBOL_CASE_AS_IS ); # These are all the keys that are # 1. Always present in %Config - lib/Config.pm #87 tie %Config # 2. Reported by 'perl -V' (the rest) my @config_vars = qw( api_subversion api_version api_versionstring archlibexp dont_use_nlink d_readlink d_symlink exe_ext inc_version_list ldlibpthname patchlevel path_sep perl_patchlevel privlibexp scriptdir sitearchexp sitelibexp subversion usevendorprefix version git_commit_id git_describe git_branch git_uncommitted_changes git_commit_id_title git_snapshot_date package revision version_patchlevel_string osname osvers archname myuname config_args hint useposix d_sigaction useithreads usemultiplicity useperlio d_sfio uselargefiles usesocks use64bitint use64bitall uselongdouble usemymalloc default_inc_excludes_dot bincompat5005 cc ccflags optimize cppflags ccversion gccversion gccosandvers intsize longsize ptrsize doublesize byteorder d_longlong longlongsize d_longdbl longdblsize ivtype ivsize nvtype nvsize lseektype lseeksize alignbytes prototype ld ldflags libpth libs perllibs libc so useshrplib libperl gnulibc_version dlsrc dlext d_dlsymun ccdlflags cccdlflags lddlflags ); my %empty_build = ( osname => "", stamp => 0, options => { %BTD }, patches => [], ); sub _make_derived { my $conf = shift; for ( [ lseektype => "Off_t" ], [ myuname => "uname" ], [ perl_patchlevel => "patch" ], ) { my ($official, $derived) = @$_; $conf->{config}{$derived} ||= $conf->{config}{$official}; $conf->{config}{$official} ||= $conf->{config}{$derived}; $conf->{derived}{$derived} = delete $conf->{config}{$derived}; } if (exists $conf->{config}{version_patchlevel_string} && !exists $conf->{config}{api_version}) { my $vps = $conf->{config}{version_patchlevel_string}; $vps =~ s{\b revision \s+ (\S+) }{}x and $conf->{config}{revision} ||= $1; $vps =~ s{\b version \s+ (\S+) }{}x and $conf->{config}{api_version} ||= $1; $vps =~ s{\b subversion \s+ (\S+) }{}x and $conf->{config}{subversion} ||= $1; $vps =~ s{\b patch \s+ (\S+) }{}x and $conf->{config}{perl_patchlevel} ||= $1; } ($conf->{config}{version_patchlevel_string} ||= join " ", map { ($_, $conf->{config}{$_} ) } grep { $conf->{config}{$_} } qw( api_version subversion perl_patchlevel )) =~ s/\bperl_//; $conf->{config}{perl_patchlevel} ||= ""; # 0 is not a valid patchlevel if ($conf->{config}{perl_patchlevel} =~ m{^git\w*-([^-]+)}i) { $conf->{config}{git_branch} ||= $1; $conf->{config}{git_describe} ||= $conf->{config}{perl_patchlevel}; } $conf->{config}{$_} ||= "undef" for grep m/^(?:use|def)/ => @config_vars; $conf; } # _make_derived sub plv2hash { my %config; my $pv = join "\n" => @_; if ($pv =~ m/^Summary of my\s+(\S+)\s+\(\s*(.*?)\s*\)/m) { $config{"package"} = $1; my $rev = $2; $rev =~ s/^ revision \s+ (\S+) \s*//x and $config{revision} = $1; $rev and $config{version_patchlevel_string} = $rev; my ($rel) = $config{"package"} =~ m{perl(\d)}; my ($vers, $subvers) = $rev =~ m{version\s+(\d+)\s+subversion\s+(\d+)}; defined $vers && defined $subvers && defined $rel and $config{version} = "$rel.$vers.$subvers"; } if ($pv =~ m/^\s+(Snapshot of:)\s+(\S+)/) { $config{git_commit_id_title} = $1; $config{git_commit_id} = $2; } # these are always last on line and can have multiple quotation styles for my $k (qw( ccflags ldflags lddlflags )) { $pv =~ s{, \s* $k \s*=\s* (.*) \s*$}{}mx or next; my $v = $1; $v =~ s/\s*,\s*$//; $v =~ s/^(['"])(.*)\1$/$2/; $config{$k} = $v; } if (my %kv = ($pv =~ m{\b (\w+) # key \s*= # assign ( '\s*[^']*?\s*' # quoted value | \S+[^=]*?\s*\n # unquoted running till end of line | \S+ # unquoted value | \s*\n # empty ) (?:,?\s+|\s*\n)? # separator (5.8.x reports did not have a ',' }gx)) { # between every kv pair while (my ($k, $v) = each %kv) { $k =~ s/\s+$//; $v =~ s/\s*\n\z//; $v =~ s/,$//; $v =~ m/^'(.*)'$/ and $v = $1; $v =~ s/\s+$//; $config{$k} = $v; } } my $build = { %empty_build }; $pv =~ m{^\s+Compiled at\s+(.*)}m and $build->{stamp} = $1; $pv =~ m{^\s+Locally applied patches:(?:\s+|\n)(.*?)(?:[\s\n]+Buil[td] under)}ms and $build->{patches} = [ split m/\n+\s*/, $1 ]; $pv =~ m{^\s+Compile-time options:(?:\s+|\n)(.*?)(?:[\s\n]+(?:Locally applied|Buil[td] under))}ms and map { $build->{options}{$_} = 1 } split m/\s+|\n/ => $1; $build->{osname} = $config{osname}; $pv =~ m{^\s+Built under\s+(.*)}m and $build->{osname} = $1; $config{osname} ||= $build->{osname}; return _make_derived ({ build => $build, environment => {}, config => \%config, derived => {}, inc => [], }); } # plv2hash sub summary { my $conf = shift || myconfig (); ref $conf eq "HASH" && exists $conf->{config} && exists $conf->{build} && ref $conf->{config} eq "HASH" && ref $conf->{build} eq "HASH" or return; my %info = map { exists $conf->{config}{$_} ? ( $_ => $conf->{config}{$_} ) : () } qw( archname osname osvers revision patchlevel subversion version cc ccversion gccversion config_args inc_version_list d_longdbl d_longlong use64bitall use64bitint useithreads uselongdouble usemultiplicity usemymalloc useperlio useshrplib doublesize intsize ivsize nvsize longdblsize longlongsize lseeksize default_inc_excludes_dot ); $info{$_}++ for grep { $conf->{build}{options}{$_} } keys %{$conf->{build}{options}}; return \%info; } # summary sub signature { my $no_md5 = "0" x 32; my $conf = summary (shift) or return $no_md5; eval { require Digest::MD5 }; $@ and return $no_md5; $conf->{cc} =~ s{.*\bccache\s+}{}; $conf->{cc} =~ s{.*[/\\]}{}; delete $conf->{config_args}; return Digest::MD5::md5_hex (join "\xFF" => map { "$_=".(defined $conf->{$_} ? $conf->{$_} : "\xFE"); } sort keys %$conf); } # signature sub myconfig { my $args = shift; my %args = ref $args eq "HASH" ? %$args : ref $args eq "ARRAY" ? @$args : (); my $build = { %empty_build }; # 5.14.0 and later provide all the information without shelling out my $stamp = eval { Config::compile_date () }; if (defined $stamp) { $stamp =~ s/^Compiled at //; $build->{osname} = $^O; $build->{stamp} = $stamp; $build->{patches} = [ Config::local_patches () ]; $build->{options}{$_} = 1 for Config::bincompat_options (), Config::non_bincompat_options (); } else { #y $pv = qx[$^X -e"sub Config::myconfig{};" -V]; my $cnf = plv2hash (qx[$^X -V]); $build->{$_} = $cnf->{build}{$_} for qw( osname stamp patches options ); } my @KEYS = keys %ENV; my %env = map { $_ => $ENV{$_} } grep m/^PERL/ => @KEYS; $args{env} and map { $env{$_} = $ENV{$_} } grep m{$args{env}} => @KEYS; my %config = map { $_ => $Config{$_} } @config_vars; return _make_derived ({ build => $build, environment => \%env, config => \%config, derived => {}, inc => \@INC, }); } # myconfig 1; __END__ package ExtUtils::MM_UWIN; use strict; our $VERSION = '7.34'; $VERSION = eval $VERSION; require ExtUtils::MM_Unix; our @ISA = qw(ExtUtils::MM_Unix); sub os_flavor { return('Unix', 'U/WIN'); } sub replace_manpage_separator { my($self, $man) = @_; $man =~ s,/+,.,g; return $man; } 1; package ExtUtils::Manifest; require Exporter; use Config; use File::Basename; use File::Copy 'copy'; use File::Find; use File::Spec 0.8; use Carp; use strict; use warnings; our $VERSION = '1.70'; our @ISA = ('Exporter'); our @EXPORT_OK = qw(mkmanifest manicheck filecheck fullcheck skipcheck manifind maniread manicopy maniadd maniskip ); our $Is_MacOS = $^O eq 'MacOS'; our $Is_VMS = $^O eq 'VMS'; our $Is_VMS_mode = 0; our $Is_VMS_lc = 0; our $Is_VMS_nodot = 0; # No dots in dir names or double dots in files if ($Is_VMS) { require VMS::Filespec if $Is_VMS; my $vms_unix_rpt; my $vms_efs; my $vms_case; $Is_VMS_mode = 1; $Is_VMS_lc = 1; $Is_VMS_nodot = 1; if (eval { local $SIG{__DIE__}; require VMS::Feature; }) { $vms_unix_rpt = VMS::Feature::current("filename_unix_report"); $vms_efs = VMS::Feature::current("efs_charset"); $vms_case = VMS::Feature::current("efs_case_preserve"); } else { my $unix_rpt = $ENV{'DECC$FILENAME_UNIX_REPORT'} || ''; my $efs_charset = $ENV{'DECC$EFS_CHARSET'} || ''; my $efs_case = $ENV{'DECC$EFS_CASE_PRESERVE'} || ''; $vms_unix_rpt = $unix_rpt =~ /^[ET1]/i; $vms_efs = $efs_charset =~ /^[ET1]/i; $vms_case = $efs_case =~ /^[ET1]/i; } $Is_VMS_lc = 0 if ($vms_case); $Is_VMS_mode = 0 if ($vms_unix_rpt); $Is_VMS_nodot = 0 if ($vms_efs); } our $Debug = $ENV{PERL_MM_MANIFEST_DEBUG} || 0; our $Verbose = defined $ENV{PERL_MM_MANIFEST_VERBOSE} ? $ENV{PERL_MM_MANIFEST_VERBOSE} : 1; our $Quiet = 0; our $MANIFEST = 'MANIFEST'; our $DEFAULT_MSKIP = File::Spec->catfile( dirname(__FILE__), "$MANIFEST.SKIP" ); sub _sort { return sort { lc $a cmp lc $b } @_; } sub mkmanifest { my $manimiss = 0; my $read = (-r 'MANIFEST' && maniread()) or $manimiss++; $read = {} if $manimiss; local *M; my $bakbase = $MANIFEST; $bakbase =~ s/\./_/g if $Is_VMS_nodot; # avoid double dots rename $MANIFEST, "$bakbase.bak" unless $manimiss; open M, "> $MANIFEST" or die "Could not open $MANIFEST: $!"; binmode M, ':raw'; my $skip = maniskip(); my $found = manifind(); my($key,$val,$file,%all); %all = (%$found, %$read); $all{$MANIFEST} = ($Is_VMS_mode ? "$MANIFEST\t\t" : '') . 'This list of files' if $manimiss; # add new MANIFEST to known file list foreach $file (_sort keys %all) { if ($skip->($file)) { # Policy: only remove files if they're listed in MANIFEST.SKIP. # Don't remove files just because they don't exist. warn "Removed from $MANIFEST: $file\n" if $Verbose and exists $read->{$file}; next; } if ($Verbose){ warn "Added to $MANIFEST: $file\n" unless exists $read->{$file}; } my $text = $all{$file}; $file = _unmacify($file); my $tabs = (5 - (length($file)+1)/8); $tabs = 1 if $tabs < 1; $tabs = 0 unless $text; if ($file =~ /\s/) { $file =~ s/([\\'])/\\$1/g; $file = "'$file'"; } print M $file, "\t" x $tabs, $text, "\n"; } close M; } # Geez, shouldn't this use File::Spec or File::Basename or something? # Why so careful about dependencies? sub clean_up_filename { my $filename = shift; $filename =~ s|^\./||; $filename =~ s/^:([^:]+)$/$1/ if $Is_MacOS; if ( $Is_VMS ) { $filename =~ s/\.$//; # trim trailing dot $filename = VMS::Filespec::unixify($filename); # unescape spaces, etc. if( $Is_VMS_lc ) { $filename = lc($filename); $filename = uc($filename) if $filename =~ /^MANIFEST(\.SKIP)?$/i; } } return $filename; } sub manifind { my $p = shift || {}; my $found = {}; my $wanted = sub { my $name = clean_up_filename($File::Find::name); warn "Debug: diskfile $name\n" if $Debug; return if -d $_; $found->{$name} = ""; }; # We have to use "$File::Find::dir/$_" in preprocess, because # $File::Find::name is unavailable. # Also, it's okay to use / here, because MANIFEST files use Unix-style # paths. find({wanted => $wanted, follow_fast => 1}, $Is_MacOS ? ":" : "."); return $found; } sub manicheck { return _check_files(); } sub filecheck { return _check_manifest(); } sub fullcheck { return [_check_files()], [_check_manifest()]; } sub skipcheck { my($p) = @_; my $found = manifind(); my $matches = maniskip(); my @skipped = (); foreach my $file (_sort keys %$found){ if (&$matches($file)){ warn "Skipping $file\n" unless $Quiet; push @skipped, $file; next; } } return @skipped; } sub _check_files { my $p = shift; my $dosnames=(defined(&Dos::UseLFN) && Dos::UseLFN()==0); my $read = maniread() || {}; my $found = manifind($p); my(@missfile) = (); foreach my $file (_sort keys %$read){ warn "Debug: manicheck checking from $MANIFEST $file\n" if $Debug; if ($dosnames){ $file = lc $file; $file =~ s=(\.(\w|-)+)=substr ($1,0,4)=ge; $file =~ s=((\w|-)+)=substr ($1,0,8)=ge; } unless ( exists $found->{$file} ) { warn "No such file: $file\n" unless $Quiet; push @missfile, $file; } } return @missfile; } sub _check_manifest { my($p) = @_; my $read = maniread() || {}; my $found = manifind($p); my $skip = maniskip(); my @missentry = (); foreach my $file (_sort keys %$found){ next if $skip->($file); warn "Debug: manicheck checking from disk $file\n" if $Debug; unless ( exists $read->{$file} ) { my $canon = $Is_MacOS ? "\t" . _unmacify($file) : ''; warn "Not in $MANIFEST: $file$canon\n" unless $Quiet; push @missentry, $file; } } return @missentry; } sub maniread { my ($mfile) = @_; $mfile ||= $MANIFEST; my $read = {}; local *M; unless (open M, "< $mfile"){ warn "Problem opening $mfile: $!"; return $read; } local $_; while (){ chomp; next if /^\s*#/; my($file, $comment); # filename may contain spaces if enclosed in '' # (in which case, \\ and \' are escapes) if (($file, $comment) = /^'((?:\\[\\']|.+)+)'\s*(.*)/) { $file =~ s/\\([\\'])/$1/g; } else { ($file, $comment) = /^(\S+)\s*(.*)/; } next unless $file; if ($Is_MacOS) { $file = _macify($file); $file =~ s/\\([0-3][0-7][0-7])/sprintf("%c", oct($1))/ge; } elsif ($Is_VMS_mode) { require File::Basename; my($base,$dir) = File::Basename::fileparse($file); # Resolve illegal file specifications in the same way as tar if ($Is_VMS_nodot) { $dir =~ tr/./_/; my(@pieces) = split(/\./,$base); if (@pieces > 2) { $base = shift(@pieces) . '.' . join('_',@pieces); } my $okfile = "$dir$base"; warn "Debug: Illegal name $file changed to $okfile\n" if $Debug; $file = $okfile; } if( $Is_VMS_lc ) { $file = lc($file); $file = uc($file) if $file =~ /^MANIFEST(\.SKIP)?$/i; } } $read->{$file} = $comment; } close M; $read; } # returns an anonymous sub that decides if an argument matches sub maniskip { my @skip ; my $mfile = shift || "$MANIFEST.SKIP"; _check_mskip_directives($mfile) if -f $mfile; local(*M, $_); open M, "< $mfile" or open M, "< $DEFAULT_MSKIP" or return sub {0}; while (){ chomp; s/\r//; $_ =~ qr{^\s*(?:(?:'([^\\']*(?:\\.[^\\']*)*)')|([^#\s]\S*))?(?:(?:\s*)|(?:\s+(.*?)\s*))$}; #my $comment = $3; my $filename = $2; if ( defined($1) ) { $filename = $1; $filename =~ s/\\(['\\])/$1/g; } next if (not defined($filename) or not $filename); push @skip, _macify($filename); } close M; return sub {0} unless (scalar @skip > 0); my $opts = $Is_VMS_mode ? '(?i)' : ''; # Make sure each entry is isolated in its own parentheses, in case # any of them contain alternations my $regex = join '|', map "(?:$_)", @skip; return sub { $_[0] =~ qr{$opts$regex} }; } # checks for the special directives # #!include_default # #!include /path/to/some/manifest.skip # in a custom MANIFEST.SKIP for, for including # the content of, respectively, the default MANIFEST.SKIP # and an external manifest.skip file sub _check_mskip_directives { my $mfile = shift; local (*M, $_); my @lines = (); my $flag = 0; unless (open M, "< $mfile") { warn "Problem opening $mfile: $!"; return; } while () { if (/^#!include_default\s*$/) { if (my @default = _include_mskip_file()) { push @lines, @default; warn "Debug: Including default MANIFEST.SKIP\n" if $Debug; $flag++; } next; } if (/^#!include\s+(.*)\s*$/) { my $external_file = $1; if (my @external = _include_mskip_file($external_file)) { push @lines, @external; warn "Debug: Including external $external_file\n" if $Debug; $flag++; } next; } push @lines, $_; } close M; return unless $flag; my $bakbase = $mfile; $bakbase =~ s/\./_/g if $Is_VMS_nodot; # avoid double dots rename $mfile, "$bakbase.bak"; warn "Debug: Saving original $mfile as $bakbase.bak\n" if $Debug; unless (open M, "> $mfile") { warn "Problem opening $mfile: $!"; return; } binmode M, ':raw'; print M $_ for (@lines); close M; return; } # returns an array containing the lines of an external # manifest.skip file, if given, or $DEFAULT_MSKIP sub _include_mskip_file { my $mskip = shift || $DEFAULT_MSKIP; unless (-f $mskip) { warn qq{Included file "$mskip" not found - skipping}; return; } local (*M, $_); unless (open M, "< $mskip") { warn "Problem opening $mskip: $!"; return; } my @lines = (); push @lines, "\n#!start included $mskip\n"; push @lines, $_ while ; close M; push @lines, "#!end included $mskip\n\n"; return @lines; } sub manicopy { my($read,$target,$how)=@_; croak "manicopy() called without target argument" unless defined $target; $how ||= 'cp'; require File::Path; require File::Basename; $target = VMS::Filespec::unixify($target) if $Is_VMS_mode; File::Path::mkpath([ $target ],! $Quiet,$Is_VMS ? undef : 0755); foreach my $file (keys %$read){ if ($Is_MacOS) { if ($file =~ m!:!) { my $dir = _maccat($target, $file); $dir =~ s/[^:]+$//; File::Path::mkpath($dir,1,0755); } cp_if_diff($file, _maccat($target, $file), $how); } else { $file = VMS::Filespec::unixify($file) if $Is_VMS_mode; if ($file =~ m!/!) { # Ilya, that hurts, I fear, or maybe not? my $dir = File::Basename::dirname($file); $dir = VMS::Filespec::unixify($dir) if $Is_VMS_mode; File::Path::mkpath(["$target/$dir"],! $Quiet,$Is_VMS ? undef : 0755); } cp_if_diff($file, "$target/$file", $how); } } } sub cp_if_diff { my($from, $to, $how)=@_; if (! -f $from) { carp "$from not found"; return; } my($diff) = 0; local(*F,*T); open(F,"< $from\0") or die "Can't read $from: $!\n"; if (open(T,"< $to\0")) { local $_; while () { $diff++,last if $_ ne ; } $diff++ unless eof(T); close T; } else { $diff++; } close F; if ($diff) { if (-e $to) { unlink($to) or confess "unlink $to: $!"; } STRICT_SWITCH: { best($from,$to), last STRICT_SWITCH if $how eq 'best'; cp($from,$to), last STRICT_SWITCH if $how eq 'cp'; ln($from,$to), last STRICT_SWITCH if $how eq 'ln'; croak("ExtUtils::Manifest::cp_if_diff " . "called with illegal how argument [$how]. " . "Legal values are 'best', 'cp', and 'ln'."); } } } sub cp { my ($srcFile, $dstFile) = @_; my ($access,$mod) = (stat $srcFile)[8,9]; copy($srcFile,$dstFile); utime $access, $mod + ($Is_VMS ? 1 : 0), $dstFile; _manicopy_chmod($srcFile, $dstFile); } sub ln { my ($srcFile, $dstFile) = @_; # Fix-me - VMS can support links. return &cp if $Is_VMS or ($^O eq 'MSWin32' and Win32::IsWin95()); link($srcFile, $dstFile); unless( _manicopy_chmod($srcFile, $dstFile) ) { unlink $dstFile; return; } 1; } # 1) Strip off all group and world permissions. # 2) Let everyone read it. # 3) If the owner can execute it, everyone can. sub _manicopy_chmod { my($srcFile, $dstFile) = @_; my $perm = 0444 | (stat $srcFile)[2] & 0700; chmod( $perm | ( $perm & 0100 ? 0111 : 0 ), $dstFile ); } # Files that are often modified in the distdir. Don't hard link them. my @Exceptions = qw(MANIFEST META.yml SIGNATURE); sub best { my ($srcFile, $dstFile) = @_; my $is_exception = grep $srcFile =~ /$_/, @Exceptions; if ($is_exception or !$Config{d_link} or -l $srcFile) { cp($srcFile, $dstFile); } else { ln($srcFile, $dstFile) or cp($srcFile, $dstFile); } } sub _macify { my($file) = @_; return $file unless $Is_MacOS; $file =~ s|^\./||; if ($file =~ m|/|) { $file =~ s|/+|:|g; $file = ":$file"; } $file; } sub _maccat { my($f1, $f2) = @_; return "$f1/$f2" unless $Is_MacOS; $f1 .= ":$f2"; $f1 =~ s/([^:]:):/$1/g; return $f1; } sub _unmacify { my($file) = @_; return $file unless $Is_MacOS; $file =~ s|^:||; $file =~ s|([/ \n])|sprintf("\\%03o", unpack("c", $1))|ge; $file =~ y|:|/|; $file; } sub maniadd { my($additions) = shift; _normalize($additions); _fix_manifest($MANIFEST); my $manifest = maniread(); my @needed = grep { !exists $manifest->{$_} } keys %$additions; return 1 unless @needed; open(MANIFEST, ">>$MANIFEST") or die "maniadd() could not open $MANIFEST: $!"; binmode MANIFEST, ':raw'; foreach my $file (_sort @needed) { my $comment = $additions->{$file} || ''; if ($file =~ /\s/) { $file =~ s/([\\'])/\\$1/g; $file = "'$file'"; } printf MANIFEST "%-40s %s\n", $file, $comment; } close MANIFEST or die "Error closing $MANIFEST: $!"; return 1; } # Make sure this MANIFEST is consistently written with native # newlines and has a terminal newline. sub _fix_manifest { my $manifest_file = shift; open MANIFEST, $MANIFEST or die "Could not open $MANIFEST: $!"; local $/; my @manifest = split /(\015\012|\012|\015)/, , -1; close MANIFEST; my $must_rewrite = ""; if ($manifest[-1] eq ""){ # sane case: last line had a terminal newline pop @manifest; for (my $i=1; $i<=$#manifest; $i+=2) { unless ($manifest[$i] eq "\n") { $must_rewrite = "not a newline at pos $i"; last; } } } else { $must_rewrite = "last line without newline"; } if ( $must_rewrite ) { 1 while unlink $MANIFEST; # avoid multiple versions on VMS open MANIFEST, ">", $MANIFEST or die "(must_rewrite=$must_rewrite) Could not open >$MANIFEST: $!"; binmode MANIFEST, ':raw'; for (my $i=0; $i<=$#manifest; $i+=2) { print MANIFEST "$manifest[$i]\n"; } close MANIFEST or die "could not write $MANIFEST: $!"; } } # UNIMPLEMENTED sub _normalize { return; } 1; package ExtUtils::Mkbootstrap; # There's just too much Dynaloader incest here to turn on strict vars. use strict 'refs'; our $VERSION = '7.34'; $VERSION = eval $VERSION; require Exporter; our @ISA = ('Exporter'); our @EXPORT = ('&Mkbootstrap'); use Config; our $Verbose = 0; sub Mkbootstrap { my($baseext, @bsloadlibs)=@_; @bsloadlibs = grep($_, @bsloadlibs); # strip empty libs print " bsloadlibs=@bsloadlibs\n" if $Verbose; # We need DynaLoader here because we and/or the *_BS file may # call dl_findfile(). We don't say `use' here because when # first building perl extensions the DynaLoader will not have # been built when MakeMaker gets first used. require DynaLoader; rename "$baseext.bs", "$baseext.bso" if -s "$baseext.bs"; if (-f "${baseext}_BS"){ $_ = "${baseext}_BS"; package DynaLoader; # execute code as if in DynaLoader local($osname, $dlsrc) = (); # avoid warnings ($osname, $dlsrc) = @Config::Config{qw(osname dlsrc)}; $bscode = ""; unshift @INC, "."; require $_; shift @INC; } if ($Config{'dlsrc'} =~ /^dl_dld/){ package DynaLoader; push(@dl_resolve_using, dl_findfile('-lc')); } my(@all) = (@bsloadlibs, @DynaLoader::dl_resolve_using); my($method) = ''; if (@all || (defined $DynaLoader::bscode && length $DynaLoader::bscode)){ open my $bs, ">", "$baseext.bs" or die "Unable to open $baseext.bs: $!"; print "Writing $baseext.bs\n"; print " containing: @all" if $Verbose; print $bs "# $baseext DynaLoader bootstrap file for $^O architecture.\n"; print $bs "# Do not edit this file, changes will be lost.\n"; print $bs "# This file was automatically generated by the\n"; print $bs "# Mkbootstrap routine in ExtUtils::Mkbootstrap (v$VERSION).\n"; if (@all) { print $bs "\@DynaLoader::dl_resolve_using = "; # If @all contains names in the form -lxxx or -Lxxx then it's asking for # runtime library location so we automatically add a call to dl_findfile() if (" @all" =~ m/ -[lLR]/){ print $bs " dl_findfile(qw(\n @all\n ));\n"; } else { print $bs " qw(@all);\n"; } } # write extra code if *_BS says so print $bs $DynaLoader::bscode if $DynaLoader::bscode; print $bs "\n1;\n"; close $bs; } } 1; __END__ package ExtUtils::MM_Win32; use strict; use ExtUtils::MakeMaker::Config; use File::Basename; use File::Spec; use ExtUtils::MakeMaker qw(neatvalue _sprintf562); require ExtUtils::MM_Any; require ExtUtils::MM_Unix; our @ISA = qw( ExtUtils::MM_Any ExtUtils::MM_Unix ); our $VERSION = '7.34'; $VERSION = eval $VERSION; $ENV{EMXSHELL} = 'sh'; # to run `commands` my ( $BORLAND, $GCC, $MSVC ) = _identify_compiler_environment( \%Config ); sub _identify_compiler_environment { my ( $config ) = @_; my $BORLAND = $config->{cc} =~ /\bbcc/i ? 1 : 0; my $GCC = $config->{cc} =~ /\bgcc\b/i ? 1 : 0; my $MSVC = $config->{cc} =~ /\b(?:cl|icl)/i ? 1 : 0; # MSVC can come as clarm.exe, icl=Intel C return ( $BORLAND, $GCC, $MSVC ); } sub dlsyms { my($self,%attribs) = @_; return '' if $self->{SKIPHASH}{'dynamic'}; $self->xs_dlsyms_iterator(\%attribs); } sub xs_dlsyms_ext { '.def'; } sub replace_manpage_separator { my($self,$man) = @_; $man =~ s,/+,.,g; $man; } sub maybe_command { my($self,$file) = @_; my @e = exists($ENV{'PATHEXT'}) ? split(/;/, $ENV{PATHEXT}) : qw(.com .exe .bat .cmd); my $e = ''; for (@e) { $e .= "\Q$_\E|" } chop $e; # see if file ends in one of the known extensions if ($file =~ /($e)$/i) { return $file if -e $file; } else { for (@e) { return "$file$_" if -e "$file$_"; } } return; } sub init_DIRFILESEP { my($self) = shift; # The ^ makes sure its not interpreted as an escape in nmake $self->{DIRFILESEP} = $self->is_make_type('nmake') ? '^\\' : $self->is_make_type('dmake') ? '\\\\' : $self->is_make_type('gmake') ? '/' : '\\'; } sub init_tools { my ($self) = @_; $self->{NOOP} ||= 'rem'; $self->{DEV_NULL} ||= '> NUL'; $self->{FIXIN} ||= $self->{PERL_CORE} ? "\$(PERLRUN) $self->{PERL_SRC}\\win32\\bin\\pl2bat.pl" : 'pl2bat.bat'; $self->SUPER::init_tools; # Setting SHELL from $Config{sh} can break dmake. Its ok without it. delete $self->{SHELL}; return; } sub init_others { my $self = shift; $self->{LD} ||= 'link'; $self->{AR} ||= 'lib'; $self->SUPER::init_others; $self->{LDLOADLIBS} ||= $Config{libs}; # -Lfoo must come first for Borland, so we put it in LDDLFLAGS if ($BORLAND) { my $libs = $self->{LDLOADLIBS}; my $libpath = ''; while ($libs =~ s/(?:^|\s)(("?)-L.+?\2)(?:\s|$)/ /) { $libpath .= ' ' if length $libpath; $libpath .= $1; } $self->{LDLOADLIBS} = $libs; $self->{LDDLFLAGS} ||= $Config{lddlflags}; $self->{LDDLFLAGS} .= " $libpath"; } return; } sub init_platform { my($self) = shift; $self->{MM_Win32_VERSION} = $VERSION; return; } sub platform_constants { my($self) = shift; my $make_frag = ''; foreach my $macro (qw(MM_Win32_VERSION)) { next unless defined $self->{$macro}; $make_frag .= "$macro = $self->{$macro}\n"; } return $make_frag; } sub specify_shell { my $self = shift; return '' unless $self->is_make_type('gmake'); "\nSHELL = $ENV{COMSPEC}\n"; } sub constants { my $self = shift; my $make_text = $self->SUPER::constants; return $make_text unless $self->is_make_type('dmake'); # dmake won't read any single "line" (even those with escaped newlines) # larger than a certain size which can be as small as 8k. PM_TO_BLIB # on large modules like DateTime::TimeZone can create lines over 32k. # So we'll crank it up to a WHOPPING 64k. # # This has to come here before all the constants and not in # platform_constants which is after constants. my $size = $self->{MAXLINELENGTH} || 800000; my $prefix = qq{ # Get dmake to read long commands like PM_TO_BLIB MAXLINELENGTH = $size }; return $prefix . $make_text; } sub special_targets { my($self) = @_; my $make_frag = $self->SUPER::special_targets; $make_frag .= <<'MAKE_FRAG' if $self->is_make_type('dmake'); .USESHELL : MAKE_FRAG return $make_frag; } sub static_lib_pure_cmd { my ($self, $from) = @_; $from =~ s/(\$\(\w+)(\))/$1:^"+"$2/g if $BORLAND; sprintf qq{\t\$(AR) %s\n}, ($BORLAND ? '$@ ' . $from : ($GCC ? '-ru $@ ' . $from : '-out:$@ ' . $from)); } sub xs_make_dynamic_lib { my ($self, $attribs, $from, $to, $todir, $ldfrom, $exportlist) = @_; my @m = sprintf '%s : %s $(MYEXTLIB) %s$(DFSEP).exists %s $(PERL_ARCHIVEDEP) $(INST_DYNAMIC_DEP)'."\n", $to, $from, $todir, $exportlist; if ($GCC) { # per https://rt.cpan.org/Ticket/Display.html?id=78395 no longer # uses dlltool - relies on post 2002 MinGW # 1 2 push @m, _sprintf562 <<'EOF', $exportlist, $ldfrom; $(LD) %1$s -o $@ $(LDDLFLAGS) %2$s $(OTHERLDFLAGS) $(MYEXTLIB) "$(PERL_ARCHIVE)" $(LDLOADLIBS) -Wl,--enable-auto-image-base EOF } elsif ($BORLAND) { my $ldargs = $self->is_make_type('dmake') ? q{"$(PERL_ARCHIVE:s,/,\,)" $(LDLOADLIBS:s,/,\,) $(MYEXTLIB:s,/,\,),} : q{"$(subst /,\,$(PERL_ARCHIVE))" $(subst /,\,$(LDLOADLIBS)) $(subst /,\,$(MYEXTLIB)),}; my $subbed; if ($exportlist eq '$(EXPORT_LIST)') { $subbed = $self->is_make_type('dmake') ? q{$(EXPORT_LIST:s,/,\,)} : q{$(subst /,\,$(EXPORT_LIST))}; } else { # in XSMULTI, exportlist is per-XS, so have to sub in perl not make ($subbed = $exportlist) =~ s#/#\\#g; } push @m, sprintf <<'EOF', $ldfrom, $ldargs . $subbed; $(LD) $(LDDLFLAGS) $(OTHERLDFLAGS) %s,$@,,%s,$(RESFILES) EOF } else { # VC push @m, sprintf <<'EOF', $ldfrom, $exportlist; $(LD) -out:$@ $(LDDLFLAGS) %s $(OTHERLDFLAGS) $(MYEXTLIB) "$(PERL_ARCHIVE)" $(LDLOADLIBS) -def:%s EOF # Embed the manifest file if it exists push(@m, q{ if exist $@.manifest mt -nologo -manifest $@.manifest -outputresource:$@;2 if exist $@.manifest del $@.manifest}); } push @m, "\n\t\$(CHMOD) \$(PERM_RWX) \$\@\n"; join '', @m; } sub xs_dynamic_lib_macros { my ($self, $attribs) = @_; my $otherldflags = $attribs->{OTHERLDFLAGS} || ($BORLAND ? 'c0d32.obj': ''); my $inst_dynamic_dep = $attribs->{INST_DYNAMIC_DEP} || ""; sprintf <<'EOF', $otherldflags, $inst_dynamic_dep; # This section creates the dynamically loadable objects from relevant # objects and possibly $(MYEXTLIB). OTHERLDFLAGS = %s INST_DYNAMIC_DEP = %s EOF } sub extra_clean_files { my $self = shift; return $GCC ? (qw(dll.base dll.exp)) : ('*.pdb'); } sub init_linker { my $self = shift; $self->{PERL_ARCHIVE} = "\$(PERL_INC)\\$Config{libperl}"; $self->{PERL_ARCHIVEDEP} = "\$(PERL_INCDEP)\\$Config{libperl}"; $self->{PERL_ARCHIVE_AFTER} = ''; $self->{EXPORT_LIST} = '$(BASEEXT).def'; } sub perl_script { my($self,$file) = @_; return $file if -r $file && -f _; return "$file.pl" if -r "$file.pl" && -f _; return "$file.plx" if -r "$file.plx" && -f _; return "$file.bat" if -r "$file.bat" && -f _; return; } sub can_dep_space { my $self = shift; 1; # with Win32::GetShortPathName } sub quote_dep { my ($self, $arg) = @_; if ($arg =~ / / and not $self->is_make_type('gmake')) { require Win32; $arg = Win32::GetShortPathName($arg); die <SUPER::quote_dep($arg); } sub xs_obj_opt { my ($self, $output_file) = @_; ($MSVC ? "/Fo" : "-o ") . $output_file; } sub pasthru { my($self) = shift; my $old = $self->SUPER::pasthru; return $old unless $self->is_make_type('nmake'); $old =~ s/(PASTHRU\s*=\s*)/$1 -nologo /; $old; } sub arch_check { my $self = shift; # Win32 is an XS module, minperl won't have it. # arch_check() is not critical, so just fake it. return 1 unless $self->can_load_xs; return $self->SUPER::arch_check( map { $self->_normalize_path_name($_) } @_); } sub _normalize_path_name { my $self = shift; my $file = shift; require Win32; my $short = Win32::GetShortPathName($file); return defined $short ? lc $short : lc $file; } sub oneliner { my($self, $cmd, $switches) = @_; $switches = [] unless defined $switches; # Strip leading and trailing newlines $cmd =~ s{^\n+}{}; $cmd =~ s{\n+$}{}; $cmd = $self->quote_literal($cmd); $cmd = $self->escape_newlines($cmd); $switches = join ' ', @$switches; return qq{\$(ABSPERLRUN) $switches -e $cmd --}; } sub quote_literal { my($self, $text, $opts) = @_; $opts->{allow_variables} = 1 unless defined $opts->{allow_variables}; # See: http://www.autohotkey.net/~deleyd/parameters/parameters.htm#CPP # Apply the Microsoft C/C++ parsing rules $text =~ s{\\\\"}{\\\\\\\\\\"}g; # \\" -> \\\\\" $text =~ s{(? \\\" $text =~ s{(? \" $text = qq{"$text"} if $text =~ /[ \t]/; # Apply the Command Prompt parsing rules (cmd.exe) my @text = split /("[^"]*")/, $text; # We should also escape parentheses, but it breaks one-liners containing # $(MACRO)s in makefiles. s{([<>|&^@!])}{^$1}g foreach grep { !/^"[^"]*"$/ } @text; $text = join('', @text); # dmake expands {{ to { and }} to }. if( $self->is_make_type('dmake') ) { $text =~ s/{/{{/g; $text =~ s/}/}}/g; } $text = $opts->{allow_variables} ? $self->escape_dollarsigns($text) : $self->escape_all_dollarsigns($text); return $text; } sub escape_newlines { my($self, $text) = @_; # Escape newlines $text =~ s{\n}{\\\n}g; return $text; } sub cd { my($self, $dir, @cmds) = @_; return $self->SUPER::cd($dir, @cmds) unless $self->is_make_type('nmake'); my $cmd = join "\n\t", map "$_", @cmds; my $updirs = $self->catdir(map { $self->updir } $self->splitdir($dir)); # No leading tab and no trailing newline makes for easier embedding. my $make_frag = sprintf <<'MAKE_FRAG', $dir, $cmd, $updirs; cd %s %s cd %s MAKE_FRAG chomp $make_frag; return $make_frag; } sub max_exec_len { my $self = shift; return $self->{_MAX_EXEC_LEN} ||= 2 * 1024; } sub os_flavor { return('Win32'); } sub cflags { my($self,$libperl)=@_; return $self->{CFLAGS} if $self->{CFLAGS}; return '' unless $self->needs_linking(); my $base = $self->SUPER::cflags($libperl); foreach (split /\n/, $base) { /^(\S*)\s*=\s*(\S*)$/ and $self->{$1} = $2; }; $self->{CCFLAGS} .= " -DPERLDLL" if ($self->{LINKTYPE} eq 'static'); return $self->{CFLAGS} = qq{ CCFLAGS = $self->{CCFLAGS} OPTIMIZE = $self->{OPTIMIZE} PERLTYPE = $self->{PERLTYPE} }; } sub make_type { my ($self) = @_; my $make = $self->make; $make = +( File::Spec->splitpath( $make ) )[-1]; $make =~ s!\.exe$!!i; if ( $make =~ m![^A-Z0-9]!i ) { ($make) = grep { m!make!i } split m![^A-Z0-9]!i, $make; } return "$make-style"; } 1; __END__ package ExtUtils::Mksymlists; use 5.006; use strict qw[ subs refs ]; # no strict 'vars'; # until filehandles are exempted use Carp; use Exporter; use Config; our @ISA = qw(Exporter); our @EXPORT = qw(&Mksymlists); our $VERSION = '7.34'; $VERSION = eval $VERSION; sub Mksymlists { my(%spec) = @_; my($osname) = $^O; croak("Insufficient information specified to Mksymlists") unless ( $spec{NAME} or ($spec{FILE} and ($spec{DL_FUNCS} or $spec{FUNCLIST})) ); $spec{DL_VARS} = [] unless $spec{DL_VARS}; ($spec{FILE} = $spec{NAME}) =~ s/.*::// unless $spec{FILE}; $spec{FUNCLIST} = [] unless $spec{FUNCLIST}; $spec{DL_FUNCS} = { $spec{NAME} => [] } unless ( ($spec{DL_FUNCS} and keys %{$spec{DL_FUNCS}}) or @{$spec{FUNCLIST}}); if (defined $spec{DL_FUNCS}) { foreach my $package (sort keys %{$spec{DL_FUNCS}}) { my($packprefix,$bootseen); ($packprefix = $package) =~ s/\W/_/g; foreach my $sym (@{$spec{DL_FUNCS}->{$package}}) { if ($sym =~ /^boot_/) { push(@{$spec{FUNCLIST}},$sym); $bootseen++; } else { push(@{$spec{FUNCLIST}},"XS_${packprefix}_$sym"); } } push(@{$spec{FUNCLIST}},"boot_$packprefix") unless $bootseen; } } # We'll need this if we ever add any OS which uses mod2fname # not as pseudo-builtin. # require DynaLoader; if (defined &DynaLoader::mod2fname and not $spec{DLBASE}) { $spec{DLBASE} = DynaLoader::mod2fname([ split(/::/,$spec{NAME}) ]); } if ($osname eq 'aix') { _write_aix(\%spec); } elsif ($osname eq 'MacOS'){ _write_aix(\%spec) } elsif ($osname eq 'VMS') { _write_vms(\%spec) } elsif ($osname eq 'os2') { _write_os2(\%spec) } elsif ($osname eq 'MSWin32') { _write_win32(\%spec) } else { croak("Don't know how to create linker option file for $osname\n"); } } sub _write_aix { my($data) = @_; rename "$data->{FILE}.exp", "$data->{FILE}.exp_old"; open( my $exp, ">", "$data->{FILE}.exp") or croak("Can't create $data->{FILE}.exp: $!\n"); print $exp join("\n",@{$data->{DL_VARS}}, "\n") if @{$data->{DL_VARS}}; print $exp join("\n",@{$data->{FUNCLIST}}, "\n") if @{$data->{FUNCLIST}}; close $exp; } sub _write_os2 { my($data) = @_; require Config; my $threaded = ($Config::Config{archname} =~ /-thread/ ? " threaded" : ""); if (not $data->{DLBASE}) { ($data->{DLBASE} = $data->{NAME}) =~ s/.*:://; $data->{DLBASE} = substr($data->{DLBASE},0,7) . '_'; } my $distname = $data->{DISTNAME} || $data->{NAME}; $distname = "Distribution $distname"; my $patchlevel = " pl$Config{perl_patchlevel}" || ''; my $comment = sprintf "Perl (v%s%s%s) module %s", $Config::Config{version}, $threaded, $patchlevel, $data->{NAME}; chomp $comment; if ($data->{INSTALLDIRS} and $data->{INSTALLDIRS} eq 'perl') { $distname = 'perl5-porters@perl.org'; $comment = "Core $comment"; } $comment = "$comment (Perl-config: $Config{config_args})"; $comment = substr($comment, 0, 200) . "...)" if length $comment > 203; rename "$data->{FILE}.def", "$data->{FILE}_def.old"; open(my $def, ">", "$data->{FILE}.def") or croak("Can't create $data->{FILE}.def: $!\n"); print $def "LIBRARY '$data->{DLBASE}' INITINSTANCE TERMINSTANCE\n"; print $def "DESCRIPTION '\@#$distname:$data->{VERSION}#\@ $comment'\n"; print $def "CODE LOADONCALL\n"; print $def "DATA LOADONCALL NONSHARED MULTIPLE\n"; print $def "EXPORTS\n "; print $def join("\n ",@{$data->{DL_VARS}}, "\n") if @{$data->{DL_VARS}}; print $def join("\n ",@{$data->{FUNCLIST}}, "\n") if @{$data->{FUNCLIST}}; _print_imports($def, $data); close $def; } sub _print_imports { my ($def, $data)= @_; my $imports= $data->{IMPORTS} or return; if ( keys %$imports ) { print $def "IMPORTS\n"; foreach my $name (sort keys %$imports) { print $def " $name=$imports->{$name}\n"; } } } sub _write_win32 { my($data) = @_; require Config; if (not $data->{DLBASE}) { ($data->{DLBASE} = $data->{NAME}) =~ s/.*:://; $data->{DLBASE} = substr($data->{DLBASE},0,7) . '_'; } rename "$data->{FILE}.def", "$data->{FILE}_def.old"; open( my $def, ">", "$data->{FILE}.def" ) or croak("Can't create $data->{FILE}.def: $!\n"); # put library name in quotes (it could be a keyword, like 'Alias') if ($Config::Config{'cc'} !~ /\bgcc/i) { print $def "LIBRARY \"$data->{DLBASE}\"\n"; } print $def "EXPORTS\n "; my @syms; # Export public symbols both with and without underscores to # ensure compatibility between DLLs from Borland C and Visual C # NOTE: DynaLoader itself only uses the names without underscores, # so this is only to cover the case when the extension DLL may be # linked to directly from C. GSAR 97-07-10 #bcc dropped in 5.16, so dont create useless extra symbols for export table unless($] >= 5.016) { if ($Config::Config{'cc'} =~ /^bcc/i) { push @syms, "_$_", "$_ = _$_" for (@{$data->{DL_VARS}}, @{$data->{FUNCLIST}}); } else { push @syms, "$_", "_$_ = $_" for (@{$data->{DL_VARS}}, @{$data->{FUNCLIST}}); } } else { push @syms, "$_" for (@{$data->{DL_VARS}}, @{$data->{FUNCLIST}}); } print $def join("\n ",@syms, "\n") if @syms; _print_imports($def, $data); close $def; } sub _write_vms { my($data) = @_; require Config; # a reminder for once we do $^O require ExtUtils::XSSymSet; my($isvax) = $Config::Config{'archname'} =~ /VAX/i; my($set) = new ExtUtils::XSSymSet; rename "$data->{FILE}.opt", "$data->{FILE}.opt_old"; open(my $opt,">", "$data->{FILE}.opt") or croak("Can't create $data->{FILE}.opt: $!\n"); # Options file declaring universal symbols # Used when linking shareable image for dynamic extension, # or when linking PerlShr into which we've added this package # as a static extension # We don't do anything to preserve order, so we won't relax # the GSMATCH criteria for a dynamic extension print $opt "case_sensitive=yes\n" if $Config::Config{d_vms_case_sensitive_symbols}; foreach my $sym (@{$data->{FUNCLIST}}) { my $safe = $set->addsym($sym); if ($isvax) { print $opt "UNIVERSAL=$safe\n" } else { print $opt "SYMBOL_VECTOR=($safe=PROCEDURE)\n"; } } foreach my $sym (@{$data->{DL_VARS}}) { my $safe = $set->addsym($sym); print $opt "PSECT_ATTR=${sym},PIC,OVR,RD,NOEXE,WRT,NOSHR\n"; if ($isvax) { print $opt "UNIVERSAL=$safe\n" } else { print $opt "SYMBOL_VECTOR=($safe=DATA)\n"; } } close $opt; } 1; __END__ #!perl use 5.006; BEGIN { pop @INC if $INC[-1] eq '.' } use strict; eval { require ExtUtils::ParseXS; 1; } or do { my $err = $@ || 'Zombie error'; my $v = $ExtUtils::ParseXS::VERSION; $v = '' if not defined $v; die "Failed to load or import from ExtUtils::ParseXS (version $v). Please check that ExtUtils::ParseXS is installed correctly and that the newest version will be found in your \@INC path: $err"; }; use Getopt::Long; my %args = (); my $usage = "Usage: xsubpp [-v] [-csuffix csuffix] [-except] [-prototypes] [-noversioncheck] [-nolinenumbers] [-nooptimize] [-noinout] [-noargtypes] [-strip|s pattern] [-typemap typemap]... file.xs\n"; Getopt::Long::Configure qw(no_auto_abbrev no_ignore_case); @ARGV = grep {$_ ne '-C++'} @ARGV; # Allow -C++ for backward compatibility GetOptions(\%args, qw(hiertype! prototypes! versioncheck! linenumbers! optimize! inout! argtypes! object_capi! except! v typemap=s@ output=s s|strip=s csuffix=s )) or die $usage; if ($args{v}) { print "xsubpp version $ExtUtils::ParseXS::VERSION\n"; exit; } @ARGV == 1 or die $usage; $args{filename} = shift @ARGV; my $pxs = ExtUtils::ParseXS->new; $pxs->process_file(%args); exit( $pxs->report_error_count() ? 1 : 0 ); __END__ =head1 NAME xsubpp - compiler to convert Perl XS code into C code =head1 SYNOPSIS B [B<-v>] [B<-except>] [B<-s pattern>] [B<-prototypes>] [B<-noversioncheck>] [B<-nolinenumbers>] [B<-nooptimize>] [B<-typemap typemap>] [B<-output filename>]... file.xs =head1 DESCRIPTION This compiler is typically run by the makefiles created by L or by L or other Perl module build tools. I will compile XS code into C code by embedding the constructs necessary to let C functions manipulate Perl values and creates the glue necessary to let Perl access those functions. The compiler uses typemaps to determine how to map C function parameters and variables to Perl values. The compiler will search for typemap files called I. It will use the following search path to find default typemaps, with the rightmost typemap taking precedence. ../../../typemap:../../typemap:../typemap:typemap It will also use a default typemap installed as C. =head1 OPTIONS Note that the C MakeMaker option may be used to add these options to any makefiles generated by MakeMaker. =over 5 =item B<-hiertype> Retains '::' in type names so that C++ hierarchical types can be mapped. =item B<-except> Adds exception handling stubs to the C code. =item B<-typemap typemap> Indicates that a user-supplied typemap should take precedence over the default typemaps. This option may be used multiple times, with the last typemap having the highest precedence. =item B<-output filename> Specifies the name of the output file to generate. If no file is specified, output will be written to standard output. =item B<-v> Prints the I version number to standard output, then exits. =item B<-prototypes> By default I will not automatically generate prototype code for all xsubs. This flag will enable prototypes. =item B<-noversioncheck> Disables the run time test that determines if the object file (derived from the C<.xs> file) and the C<.pm> files have the same version number. =item B<-nolinenumbers> Prevents the inclusion of '#line' directives in the output. =item B<-nooptimize> Disables certain optimizations. The only optimization that is currently affected is the use of Is by the output C code (see L). This may significantly slow down the generated code, but this is the way B of 5.005 and earlier operated. =item B<-noinout> Disable recognition of C, C and C declarations. =item B<-noargtypes> Disable recognition of ANSI-like descriptions of function signature. =item B<-C++> Currently doesn't do anything at all. This flag has been a no-op for many versions of perl, at least as far back as perl5.003_07. It's allowed here for backwards compatibility. =item B<-s=...> or B<-strip=...> I If specified, the given string will be stripped off from the beginning of the C function name in the generated XS functions (if it starts with that prefix). This only applies to XSUBs without C or C blocks. For example, the XS: void foo_bar(int i); when C is invoked with C<-s foo_> will install a C function in Perl, but really call C in C. Most of the time, this is the opposite of what you want and failure modes are somewhat obscure, so please avoid this option where possible. =back =head1 ENVIRONMENT No environment variables are used. =head1 AUTHOR Originally by Larry Wall. Turned into the C module by Ken Williams. =head1 MODIFICATION HISTORY See the file F. =head1 SEE ALSO perl(1), perlxs(1), perlxstut(1), ExtUtils::ParseXS =cut package ExtUtils::MM_Win95; use strict; our $VERSION = '7.34'; $VERSION = eval $VERSION; require ExtUtils::MM_Win32; our @ISA = qw(ExtUtils::MM_Win32); use ExtUtils::MakeMaker::Config; sub max_exec_len { my $self = shift; return $self->{_MAX_EXEC_LEN} ||= 1024; } sub os_flavor { my $self = shift; return ($self->SUPER::os_flavor, 'Win9x'); } 1; package ExtUtils::Embed; require Exporter; use Config; require File::Spec; our ( @Extensions, $opt_o, $opt_s ); use strict; # This is not a dual-life module, so no need for development version numbers our $VERSION = '1.35'; our @ISA = qw(Exporter); our @EXPORT = qw(&xsinit &ldopts &ccopts &ccflags &ccdlflags &perl_inc &xsi_header &xsi_protos &xsi_body); our $Verbose = 0; our $lib_ext = $Config{lib_ext} || '.a'; sub is_cmd { $0 eq '-e' } sub my_return { my $val = shift; if(is_cmd) { print $val; } else { return $val; } } sub xsinit { my($file, $std, $mods) = @_; my($fh,@mods,%seen); $file ||= "perlxsi.c"; my $xsinit_proto = "pTHX"; if (@_) { @mods = @$mods if $mods; } else { require Getopt::Std; Getopt::Std::getopts('o:s:'); $file = $opt_o if defined $opt_o; $std = $opt_s if defined $opt_s; @mods = @ARGV; } $std = 1 unless scalar @mods; if ($file eq "STDOUT") { $fh = \*STDOUT; } else { open $fh, '>', $file or die "Can't open '$file': $!"; } push(@mods, static_ext()) if defined $std; @mods = grep(!$seen{$_}++, @mods); print $fh &xsi_header(); print $fh "\nEXTERN_C void xs_init ($xsinit_proto);\n\n"; print $fh &xsi_protos(@mods); print $fh "\nEXTERN_C void\nxs_init($xsinit_proto)\n{\n"; print $fh &xsi_body(@mods); print $fh "}\n"; } sub xsi_header { return <catdir(@ns); print STDERR "searching for '$sub${lib_ext}'\n" if $Verbose; foreach (@path) { next unless -e ($archive = File::Spec->catdir($_,"auto",$root,"$sub$lib_ext")); push @archives, $archive; if(-e ($extra = File::Spec->catdir($_,"auto",$root,"extralibs.ld"))) { local(*FH); if(open(FH, '<', $extra)) { my($libs) = ; chomp $libs; push @potential_libs, split /\s+/, $libs; } else { warn "Couldn't open '$extra'"; } } last; } } #print STDERR "\@potential_libs = @potential_libs\n"; my $libperl; if ($^O eq 'MSWin32') { $libperl = $Config{libperl}; } elsif ($^O eq 'os390' && $Config{usedl}) { # Nothing for OS/390 (z/OS) dynamic. } else { $libperl = (grep(/^-l\w*perl\w*$/, @link_args))[0] || ($Config{libperl} =~ /^lib(\w+)(\Q$lib_ext\E|\.\Q$Config{dlext}\E)$/ ? "-l$1" : '') || "-lperl"; } my $lpath = File::Spec->catdir($Config{archlibexp}, 'CORE'); $lpath = qq["$lpath"] if $^O eq 'MSWin32'; my($extralibs, $bsloadlibs, $ldloadlibs, $ld_run_path) = MM->ext(join ' ', "-L$lpath", $libperl, @potential_libs); my $ld_or_bs = $bsloadlibs || $ldloadlibs; print STDERR "bs: $bsloadlibs ** ld: $ldloadlibs" if $Verbose; my $ccdlflags = _ccdlflags(); my $ldflags = _ldflags(); my $linkage = "$ccdlflags $ldflags @archives $ld_or_bs"; print STDERR "ldopts: '$linkage'\n" if $Verbose; return $linkage if scalar @_; my_return("$linkage\n"); } sub ccflags { my $ccflags = _ccflags(); my_return(" $ccflags "); } sub ccdlflags { my $ccdlflags = _ccdlflags(); my_return(" $ccdlflags "); } sub perl_inc { my $dir = File::Spec->catdir($Config{archlibexp}, 'CORE'); $dir = qq["$dir"] if $^O eq 'MSWin32'; my_return(" -I$dir "); } sub ccopts { ccflags . perl_inc; } sub canon { my($as, @ext) = @_; foreach(@ext) { # might be X::Y or lib/auto/X/Y/Y.a next if s!::!/!g; s!^(?:lib|ext|dist|cpan)/(?:auto/)?!!; s!/\w+\.\w+$!!; } if ($as ne '/') { s!/!$as!g foreach @ext; } @ext; } __END__ # basic C types int T_IV unsigned T_UV unsigned int T_UV long T_IV unsigned long T_UV short T_IV unsigned short T_UV char T_CHAR unsigned char T_U_CHAR char * T_PV unsigned char * T_PV const char * T_PV caddr_t T_PV wchar_t * T_PV wchar_t T_IV # bool_t is defined in bool_t T_IV size_t T_UV ssize_t T_IV time_t T_NV unsigned long * T_OPAQUEPTR char ** T_PACKEDARRAY void * T_PTR Time_t * T_PV SV * T_SV # These are the backwards-compatibility AV*/HV* typemaps that # do not decrement refcounts. Locally override with # "AV* T_AVREF_REFCOUNT_FIXED", "HV* T_HVREF_REFCOUNT_FIXED", # "CV* T_CVREF_REFCOUNT_FIXED", "SVREF T_SVREF_REFCOUNT_FIXED", # to get the fixed versions. SVREF T_SVREF CV * T_CVREF AV * T_AVREF HV * T_HVREF IV T_IV UV T_UV NV T_NV I32 T_IV I16 T_IV I8 T_IV STRLEN T_UV U32 T_U_LONG U16 T_U_SHORT U8 T_UV Result T_U_CHAR Boolean T_BOOL float T_FLOAT double T_DOUBLE SysRet T_SYSRET SysRetLong T_SYSRET FILE * T_STDIO PerlIO * T_INOUT FileHandle T_PTROBJ InputStream T_IN InOutStream T_INOUT OutputStream T_OUT bool T_BOOL ############################################################################# INPUT T_SV $var = $arg T_SVREF STMT_START { SV* const xsub_tmp_sv = $arg; SvGETMAGIC(xsub_tmp_sv); if (SvROK(xsub_tmp_sv)){ $var = SvRV(xsub_tmp_sv); } else{ Perl_croak_nocontext(\"%s: %s is not a reference\", ${$ALIAS?\q[GvNAME(CvGV(cv))]:\qq[\"$pname\"]}, \"$var\"); } } STMT_END T_SVREF_REFCOUNT_FIXED STMT_START { SV* const xsub_tmp_sv = $arg; SvGETMAGIC(xsub_tmp_sv); if (SvROK(xsub_tmp_sv)){ $var = SvRV(xsub_tmp_sv); } else{ Perl_croak_nocontext(\"%s: %s is not a reference\", ${$ALIAS?\q[GvNAME(CvGV(cv))]:\qq[\"$pname\"]}, \"$var\"); } } STMT_END T_AVREF STMT_START { SV* const xsub_tmp_sv = $arg; SvGETMAGIC(xsub_tmp_sv); if (SvROK(xsub_tmp_sv) && SvTYPE(SvRV(xsub_tmp_sv)) == SVt_PVAV){ $var = (AV*)SvRV(xsub_tmp_sv); } else{ Perl_croak_nocontext(\"%s: %s is not an ARRAY reference\", ${$ALIAS?\q[GvNAME(CvGV(cv))]:\qq[\"$pname\"]}, \"$var\"); } } STMT_END T_AVREF_REFCOUNT_FIXED STMT_START { SV* const xsub_tmp_sv = $arg; SvGETMAGIC(xsub_tmp_sv); if (SvROK(xsub_tmp_sv) && SvTYPE(SvRV(xsub_tmp_sv)) == SVt_PVAV){ $var = (AV*)SvRV(xsub_tmp_sv); } else{ Perl_croak_nocontext(\"%s: %s is not an ARRAY reference\", ${$ALIAS?\q[GvNAME(CvGV(cv))]:\qq[\"$pname\"]}, \"$var\"); } } STMT_END T_HVREF STMT_START { SV* const xsub_tmp_sv = $arg; SvGETMAGIC(xsub_tmp_sv); if (SvROK(xsub_tmp_sv) && SvTYPE(SvRV(xsub_tmp_sv)) == SVt_PVHV){ $var = (HV*)SvRV(xsub_tmp_sv); } else{ Perl_croak_nocontext(\"%s: %s is not a HASH reference\", ${$ALIAS?\q[GvNAME(CvGV(cv))]:\qq[\"$pname\"]}, \"$var\"); } } STMT_END T_HVREF_REFCOUNT_FIXED STMT_START { SV* const xsub_tmp_sv = $arg; SvGETMAGIC(xsub_tmp_sv); if (SvROK(xsub_tmp_sv) && SvTYPE(SvRV(xsub_tmp_sv)) == SVt_PVHV){ $var = (HV*)SvRV(xsub_tmp_sv); } else{ Perl_croak_nocontext(\"%s: %s is not a HASH reference\", ${$ALIAS?\q[GvNAME(CvGV(cv))]:\qq[\"$pname\"]}, \"$var\"); } } STMT_END T_CVREF STMT_START { HV *st; GV *gvp; SV * const xsub_tmp_sv = $arg; SvGETMAGIC(xsub_tmp_sv); $var = sv_2cv(xsub_tmp_sv, &st, &gvp, 0); if (!$var) { Perl_croak_nocontext(\"%s: %s is not a CODE reference\", ${$ALIAS?\q[GvNAME(CvGV(cv))]:\qq[\"$pname\"]}, \"$var\"); } } STMT_END T_CVREF_REFCOUNT_FIXED STMT_START { HV *st; GV *gvp; SV * const xsub_tmp_sv = $arg; SvGETMAGIC(xsub_tmp_sv); $var = sv_2cv(xsub_tmp_sv, &st, &gvp, 0); if (!$var) { Perl_croak_nocontext(\"%s: %s is not a CODE reference\", ${$ALIAS?\q[GvNAME(CvGV(cv))]:\qq[\"$pname\"]}, \"$var\"); } } STMT_END T_SYSRET $var NOT IMPLEMENTED T_UV $var = ($type)SvUV($arg) T_IV $var = ($type)SvIV($arg) T_INT $var = (int)SvIV($arg) T_ENUM $var = ($type)SvIV($arg) T_BOOL $var = (bool)SvTRUE($arg) T_U_INT $var = (unsigned int)SvUV($arg) T_SHORT $var = (short)SvIV($arg) T_U_SHORT $var = (unsigned short)SvUV($arg) T_LONG $var = (long)SvIV($arg) T_U_LONG $var = (unsigned long)SvUV($arg) T_CHAR $var = (char)*SvPV_nolen($arg) T_U_CHAR $var = (unsigned char)SvUV($arg) T_FLOAT $var = (float)SvNV($arg) T_NV $var = ($type)SvNV($arg) T_DOUBLE $var = (double)SvNV($arg) T_PV $var = ($type)SvPV_nolen($arg) T_PTR $var = INT2PTR($type,SvIV($arg)) T_PTRREF if (SvROK($arg)) { IV tmp = SvIV((SV*)SvRV($arg)); $var = INT2PTR($type,tmp); } else Perl_croak_nocontext(\"%s: %s is not a reference\", ${$ALIAS?\q[GvNAME(CvGV(cv))]:\qq[\"$pname\"]}, \"$var\") T_REF_IV_REF if (sv_isa($arg, \"${ntype}\")) { IV tmp = SvIV((SV*)SvRV($arg)); $var = *INT2PTR($type *, tmp); } else Perl_croak_nocontext(\"%s: %s is not of type %s\", ${$ALIAS?\q[GvNAME(CvGV(cv))]:\qq[\"$pname\"]}, \"$var\", \"$ntype\") T_REF_IV_PTR if (sv_isa($arg, \"${ntype}\")) { IV tmp = SvIV((SV*)SvRV($arg)); $var = INT2PTR($type, tmp); } else Perl_croak_nocontext(\"%s: %s is not of type %s\", ${$ALIAS?\q[GvNAME(CvGV(cv))]:\qq[\"$pname\"]}, \"$var\", \"$ntype\") T_PTROBJ if (SvROK($arg) && sv_derived_from($arg, \"${ntype}\")) { IV tmp = SvIV((SV*)SvRV($arg)); $var = INT2PTR($type,tmp); } else Perl_croak_nocontext(\"%s: %s is not of type %s\", ${$ALIAS?\q[GvNAME(CvGV(cv))]:\qq[\"$pname\"]}, \"$var\", \"$ntype\") T_PTRDESC if (sv_isa($arg, \"${ntype}\")) { IV tmp = SvIV((SV*)SvRV($arg)); ${type}_desc = (\U${type}_DESC\E*) tmp; $var = ${type}_desc->ptr; } else Perl_croak_nocontext(\"%s: %s is not of type %s\", ${$ALIAS?\q[GvNAME(CvGV(cv))]:\qq[\"$pname\"]}, \"$var\", \"$ntype\") T_REFREF if (SvROK($arg)) { IV tmp = SvIV((SV*)SvRV($arg)); $var = *INT2PTR($type,tmp); } else Perl_croak_nocontext(\"%s: %s is not a reference\", ${$ALIAS?\q[GvNAME(CvGV(cv))]:\qq[\"$pname\"]}, \"$var\") T_REFOBJ if (sv_isa($arg, \"${ntype}\")) { IV tmp = SvIV((SV*)SvRV($arg)); $var = *INT2PTR($type,tmp); } else Perl_croak_nocontext(\"%s: %s is not of type %s\", ${$ALIAS?\q[GvNAME(CvGV(cv))]:\qq[\"$pname\"]}, \"$var\", \"$ntype\") T_OPAQUE $var = *($type *)SvPV_nolen($arg) T_OPAQUEPTR $var = ($type)SvPV_nolen($arg) T_PACKED $var = XS_unpack_$ntype($arg) T_PACKEDARRAY $var = XS_unpack_$ntype($arg) T_ARRAY U32 ix_$var = $argoff; $var = $ntype(items -= $argoff); while (items--) { DO_ARRAY_ELEM; ix_$var++; } /* this is the number of elements in the array */ ix_$var -= $argoff T_STDIO $var = PerlIO_findFILE(IoIFP(sv_2io($arg))) T_IN $var = IoIFP(sv_2io($arg)) T_INOUT $var = IoIFP(sv_2io($arg)) T_OUT $var = IoOFP(sv_2io($arg)) ############################################################################# OUTPUT T_SV $arg = $var; T_SVREF $arg = newRV((SV*)$var); T_SVREF_REFCOUNT_FIXED $arg = newRV_noinc((SV*)$var); T_AVREF $arg = newRV((SV*)$var); T_AVREF_REFCOUNT_FIXED $arg = newRV_noinc((SV*)$var); T_HVREF $arg = newRV((SV*)$var); T_HVREF_REFCOUNT_FIXED $arg = newRV_noinc((SV*)$var); T_CVREF $arg = newRV((SV*)$var); T_CVREF_REFCOUNT_FIXED $arg = newRV_noinc((SV*)$var); T_IV sv_setiv($arg, (IV)$var); T_UV sv_setuv($arg, (UV)$var); T_INT sv_setiv($arg, (IV)$var); T_SYSRET if ($var != -1) { if ($var == 0) sv_setpvn($arg, "0 but true", 10); else sv_setiv($arg, (IV)$var); } T_ENUM sv_setiv($arg, (IV)$var); T_BOOL ${"$var" eq "RETVAL" ? \"$arg = boolSV($var);" : \"sv_setsv($arg, boolSV($var));"} T_U_INT sv_setuv($arg, (UV)$var); T_SHORT sv_setiv($arg, (IV)$var); T_U_SHORT sv_setuv($arg, (UV)$var); T_LONG sv_setiv($arg, (IV)$var); T_U_LONG sv_setuv($arg, (UV)$var); T_CHAR sv_setpvn($arg, (char *)&$var, 1); T_U_CHAR sv_setuv($arg, (UV)$var); T_FLOAT sv_setnv($arg, (double)$var); T_NV sv_setnv($arg, (NV)$var); T_DOUBLE sv_setnv($arg, (double)$var); T_PV sv_setpv((SV*)$arg, $var); T_PTR sv_setiv($arg, PTR2IV($var)); T_PTRREF sv_setref_pv($arg, Nullch, (void*)$var); T_REF_IV_REF sv_setref_pv($arg, \"${ntype}\", (void*)new $ntype($var)); T_REF_IV_PTR sv_setref_pv($arg, \"${ntype}\", (void*)$var); T_PTROBJ sv_setref_pv($arg, \"${ntype}\", (void*)$var); T_PTRDESC sv_setref_pv($arg, \"${ntype}\", (void*)new\U${type}_DESC\E($var)); T_REFREF NOT_IMPLEMENTED T_REFOBJ NOT IMPLEMENTED T_OPAQUE sv_setpvn($arg, (char *)&$var, sizeof($var)); T_OPAQUEPTR sv_setpvn($arg, (char *)$var, sizeof(*$var)); T_PACKED XS_pack_$ntype($arg, $var); T_PACKEDARRAY XS_pack_$ntype($arg, $var, count_$ntype); T_ARRAY { U32 ix_$var; SSize_t extend_size = /* The weird way this is written is because g++ is dumb * enough to warn "comparison is always false" on something * like: * * sizeof(a) > sizeof(b) && a > B_t_MAX * * (where the LH condition is false) */ (size_$var > (sizeof(size_$var) > sizeof(SSize_t) ? SSize_t_MAX : size_$var)) ? -1 : (SSize_t)size_$var; EXTEND(SP, extend_size); for (ix_$var = 0; ix_$var < size_$var; ix_$var++) { ST(ix_$var) = sv_newmortal(); DO_ARRAY_ELEM } } T_STDIO { GV *gv = (GV *)sv_newmortal(); PerlIO *fp = PerlIO_importFILE($var,0); gv_init_pvn(gv, gv_stashpvs("$Package",1),"__ANONIO__",10,0); if ( fp && do_open(gv, "+<&", 3, FALSE, 0, 0, fp) ) { SV *rv = newRV_inc((SV*)gv); rv = sv_bless(rv, GvSTASH(gv)); ${"$var" eq "RETVAL" ? \"$arg = sv_2mortal(rv);" : \"sv_setsv($arg, rv);\n\t\tSvREFCNT_dec_NN(rv);"} }${"$var" ne "RETVAL" ? \" else sv_setsv($arg, &PL_sv_undef);\n" : \""} } T_IN { GV *gv = (GV *)sv_newmortal(); gv_init_pvn(gv, gv_stashpvs("$Package",1),"__ANONIO__",10,0); if ( do_open(gv, "<&", 2, FALSE, 0, 0, $var) ) { SV *rv = newRV_inc((SV*)gv); rv = sv_bless(rv, GvSTASH(gv)); ${"$var" eq "RETVAL" ? \"$arg = sv_2mortal(rv);" : \"sv_setsv($arg, rv);\n\t\tSvREFCNT_dec_NN(rv);"} }${"$var" ne "RETVAL" ? \" else sv_setsv($arg, &PL_sv_undef);\n" : \""} } T_INOUT { GV *gv = (GV *)sv_newmortal(); gv_init_pvn(gv, gv_stashpvs("$Package",1),"__ANONIO__",10,0); if ( do_open(gv, "+<&", 3, FALSE, 0, 0, $var) ) { SV *rv = newRV_inc((SV*)gv); rv = sv_bless(rv, GvSTASH(gv)); ${"$var" eq "RETVAL" ? \"$arg = sv_2mortal(rv);" : \"sv_setsv($arg, rv);\n\t\tSvREFCNT_dec_NN(rv);"} }${"$var" ne "RETVAL" ? \" else sv_setsv($arg, &PL_sv_undef);\n" : \""} } T_OUT { GV *gv = (GV *)sv_newmortal(); gv_init_pvn(gv, gv_stashpvs("$Package",1),"__ANONIO__",10,0); if ( do_open(gv, "+>&", 3, FALSE, 0, 0, $var) ) { SV *rv = newRV_inc((SV*)gv); rv = sv_bless(rv, GvSTASH(gv)); ${"$var" eq "RETVAL" ? \"$arg = sv_2mortal(rv);" : \"sv_setsv($arg, rv);\n\t\tSvREFCNT_dec_NN(rv);"} }${"$var" ne "RETVAL" ? \" else sv_setsv($arg, &PL_sv_undef);\n" : \""} } package ExtUtils::MM_AIX; use strict; our $VERSION = '7.34'; $VERSION = eval $VERSION; use ExtUtils::MakeMaker::Config; require ExtUtils::MM_Unix; our @ISA = qw(ExtUtils::MM_Unix); sub dlsyms { my($self,%attribs) = @_; return '' unless $self->needs_linking; join "\n", $self->xs_dlsyms_iterator(\%attribs); } sub xs_dlsyms_ext { '.exp'; } sub xs_dlsyms_arg { my($self, $file) = @_; return qq{-bE:${file}}; } sub init_others { my $self = shift; $self->SUPER::init_others; # perl "hints" add -bE:$(BASEEXT).exp to LDDLFLAGS. strip that out # so right value can be added by xs_make_dynamic_lib to work for XSMULTI $self->{LDDLFLAGS} ||= $Config{lddlflags}; $self->{LDDLFLAGS} =~ s#(\s*)\S*\Q$(BASEEXT)\E\S*(\s*)#$1$2#; return; } 1; package ExtUtils::Constant; use vars qw (@ISA $VERSION @EXPORT_OK %EXPORT_TAGS); $VERSION = '0.25'; if ($] >= 5.006) { eval "use warnings; 1" or die $@; } use strict; use Carp qw(croak cluck); use Exporter; use ExtUtils::Constant::Utils qw(C_stringify); use ExtUtils::Constant::XS qw(%XS_Constant %XS_TypeSet); @ISA = 'Exporter'; %EXPORT_TAGS = ( 'all' => [ qw( XS_constant constant_types return_clause memEQ_clause C_stringify C_constant autoload WriteConstants WriteMakefileSnippet ) ] ); @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } ); sub constant_types { ExtUtils::Constant::XS->header(); } sub memEQ_clause { cluck "ExtUtils::Constant::memEQ_clause is deprecated"; ExtUtils::Constant::XS->memEQ_clause({name=>$_[0], checked_at=>$_[1], indent=>$_[2]}); } sub return_clause ($$) { cluck "ExtUtils::Constant::return_clause is deprecated"; my $indent = shift; ExtUtils::Constant::XS->return_clause({indent=>$indent}, @_); } sub switch_clause { cluck "ExtUtils::Constant::switch_clause is deprecated"; my $indent = shift; my $comment = shift; ExtUtils::Constant::XS->switch_clause({indent=>$indent, comment=>$comment}, @_); } sub C_constant { my ($package, $subname, $default_type, $what, $indent, $breakout, @items) = @_; ExtUtils::Constant::XS->C_constant({package => $package, subname => $subname, default_type => $default_type, types => $what, indent => $indent, breakout => $breakout}, @items); } sub XS_constant { my $package = shift; my $what = shift; my $XS_subname = shift; my $C_subname = shift; $XS_subname ||= 'constant'; $C_subname ||= $XS_subname; if (!ref $what) { # Convert line of the form IV,UV,NV to hash $what = {map {$_ => 1} split /,\s*/, ($what)}; } my $params = ExtUtils::Constant::XS->params ($what); my $type; my $xs = <<"EOT"; void $XS_subname(sv) PREINIT: #ifdef dXSTARG dXSTARG; /* Faster if we have it. */ #else dTARGET; #endif STRLEN len; int type; EOT if ($params->{IV}) { $xs .= " IV iv = 0; /* avoid uninit var warning */\n"; } else { $xs .= " /* IV\t\tiv;\tUncomment this if you need to return IVs */\n"; } if ($params->{NV}) { $xs .= " NV nv = 0.0; /* avoid uninit var warning */\n"; } else { $xs .= " /* NV\t\tnv;\tUncomment this if you need to return NVs */\n"; } if ($params->{PV}) { $xs .= " const char *pv = NULL; /* avoid uninit var warning */\n"; } else { $xs .= " /* const char\t*pv;\tUncomment this if you need to return PVs */\n"; } $xs .= << 'EOT'; INPUT: SV * sv; const char * s = SvPV(sv, len); EOT if ($params->{''}) { $xs .= << 'EOT'; INPUT: int utf8 = SvUTF8(sv); EOT } $xs .= << 'EOT'; PPCODE: EOT if ($params->{IV} xor $params->{NV}) { $xs .= << "EOT"; /* Change this to $C_subname(aTHX_ s, len, &iv, &nv); if you need to return both NVs and IVs */ EOT } $xs .= " type = $C_subname(aTHX_ s, len"; $xs .= ', utf8' if $params->{''}; $xs .= ', &iv' if $params->{IV}; $xs .= ', &nv' if $params->{NV}; $xs .= ', &pv' if $params->{PV}; $xs .= ', &sv' if $params->{SV}; $xs .= ");\n"; # If anyone is insane enough to suggest a package name containing % my $package_sprintf_safe = $package; $package_sprintf_safe =~ s/%/%%/g; $xs .= << "EOT"; /* Return 1 or 2 items. First is error message, or undef if no error. Second, if present, is found value */ switch (type) { case PERL_constant_NOTFOUND: sv = sv_2mortal(newSVpvf("%s is not a valid $package_sprintf_safe macro", s)); PUSHs(sv); break; case PERL_constant_NOTDEF: sv = sv_2mortal(newSVpvf( "Your vendor has not defined $package_sprintf_safe macro %s, used", s)); PUSHs(sv); break; EOT foreach $type (sort keys %XS_Constant) { # '' marks utf8 flag needed. next if $type eq ''; $xs .= "\t/* Uncomment this if you need to return ${type}s\n" unless $what->{$type}; $xs .= " case PERL_constant_IS$type:\n"; if (length $XS_Constant{$type}) { $xs .= << "EOT"; EXTEND(SP, 2); PUSHs(&PL_sv_undef); $XS_Constant{$type}; EOT } else { # Do nothing. return (), which will be correctly interpreted as # (undef, undef) } $xs .= " break;\n"; unless ($what->{$type}) { chop $xs; # Yes, another need for chop not chomp. $xs .= " */\n"; } } $xs .= << "EOT"; default: sv = sv_2mortal(newSVpvf( "Unexpected return type %d while processing $package_sprintf_safe macro %s, used", type, s)); PUSHs(sv); } EOT return $xs; } # ' # Grr. syntax highlighters that don't grok pod. sub autoload { my ($module, $compat_version, $autoloader) = @_; $compat_version ||= $]; croak "Can't maintain compatibility back as far as version $compat_version" if $compat_version < 5; my $func = "sub AUTOLOAD {\n" . " # This AUTOLOAD is used to 'autoload' constants from the constant()\n" . " # XS function."; $func .= " If a constant is not found then control is passed\n" . " # to the AUTOLOAD in AutoLoader." if $autoloader; $func .= "\n\n" . " my \$constname;\n"; $func .= " our \$AUTOLOAD;\n" if ($compat_version >= 5.006); $func .= <<"EOT"; (\$constname = \$AUTOLOAD) =~ s/.*:://; croak "&${module}::constant not defined" if \$constname eq 'constant'; my (\$error, \$val) = constant(\$constname); EOT if ($autoloader) { $func .= <<'EOT'; if ($error) { if ($error =~ /is not a valid/) { $AutoLoader::AUTOLOAD = $AUTOLOAD; goto &AutoLoader::AUTOLOAD; } else { croak $error; } } EOT } else { $func .= " if (\$error) { croak \$error; }\n"; } $func .= <<'END'; { no strict 'refs'; # Fixed between 5.005_53 and 5.005_61 #XXX if ($] >= 5.00561) { #XXX *$AUTOLOAD = sub () { $val }; #XXX } #XXX else { *$AUTOLOAD = sub { $val }; #XXX } } goto &$AUTOLOAD; } END return $func; } sub WriteMakefileSnippet { my %args = @_; my $indent = $args{INDENT} || 2; my $result = <<"EOT"; ExtUtils::Constant::WriteConstants( NAME => '$args{NAME}', NAMES => \\\@names, DEFAULT_TYPE => '$args{DEFAULT_TYPE}', EOT foreach (qw (C_FILE XS_FILE)) { next unless exists $args{$_}; $result .= sprintf " %-12s => '%s',\n", $_, $args{$_}; } $result .= <<'EOT'; ); EOT $result =~ s/^/' 'x$indent/gem; return ExtUtils::Constant::XS->dump_names({default_type=>$args{DEFAULT_TYPE}, indent=>$indent,}, @{$args{NAMES}}) . $result; } sub WriteConstants { my %ARGS = ( # defaults C_FILE => 'const-c.inc', XS_FILE => 'const-xs.inc', XS_SUBNAME => 'constant', DEFAULT_TYPE => 'IV', @_); $ARGS{C_SUBNAME} ||= $ARGS{XS_SUBNAME}; # No-one sane will have C_SUBNAME eq '0' croak "Module name not specified" unless length $ARGS{NAME}; # Do this before creating (empty) files, in case it fails: require ExtUtils::Constant::ProxySubs if $ARGS{PROXYSUBS}; my $c_fh = $ARGS{C_FH}; if (!$c_fh) { if ($] <= 5.008) { # We need these little games, rather than doing things # unconditionally, because we're used in core Makefile.PLs before # IO is available (needed by filehandle), but also we want to work on # older perls where undefined scalars do not automatically turn into # anonymous file handles. require FileHandle; $c_fh = FileHandle->new(); } open $c_fh, ">$ARGS{C_FILE}" or die "Can't open $ARGS{C_FILE}: $!"; } my $xs_fh = $ARGS{XS_FH}; if (!$xs_fh) { if ($] <= 5.008) { require FileHandle; $xs_fh = FileHandle->new(); } open $xs_fh, ">$ARGS{XS_FILE}" or die "Can't open $ARGS{XS_FILE}: $!"; } # As this subroutine is intended to make code that isn't edited, there's no # need for the user to specify any types that aren't found in the list of # names. if ($ARGS{PROXYSUBS}) { $ARGS{C_FH} = $c_fh; $ARGS{XS_FH} = $xs_fh; ExtUtils::Constant::ProxySubs->WriteConstants(%ARGS); } else { my $types = {}; print $c_fh constant_types(); # macro defs print $c_fh "\n"; # indent is still undef. Until anyone implements indent style rules with # it. foreach (ExtUtils::Constant::XS->C_constant({package => $ARGS{NAME}, subname => $ARGS{C_SUBNAME}, default_type => $ARGS{DEFAULT_TYPE}, types => $types, breakout => $ARGS{BREAKOUT_AT}}, @{$ARGS{NAMES}})) { print $c_fh $_, "\n"; # C constant subs } print $xs_fh XS_constant ($ARGS{NAME}, $types, $ARGS{XS_SUBNAME}, $ARGS{C_SUBNAME}); } close $c_fh or warn "Error closing $ARGS{C_FILE}: $!" unless $ARGS{C_FH}; close $xs_fh or warn "Error closing $ARGS{XS_FILE}: $!" unless $ARGS{XS_FH}; } 1; __END__ package ExtUtils::MM_OS2; use strict; use ExtUtils::MakeMaker qw(neatvalue); use File::Spec; our $VERSION = '7.34'; $VERSION = eval $VERSION; require ExtUtils::MM_Any; require ExtUtils::MM_Unix; our @ISA = qw(ExtUtils::MM_Any ExtUtils::MM_Unix); sub init_dist { my($self) = @_; $self->{TO_UNIX} ||= <<'MAKE_TEXT'; $(NOECHO) $(TEST_F) tmp.zip && $(RM_F) tmp.zip; $(ZIP) -ll -mr tmp.zip $(DISTVNAME) && unzip -o tmp.zip && $(RM_F) tmp.zip MAKE_TEXT $self->SUPER::init_dist; } sub dlsyms { my($self,%attribs) = @_; if ($self->{IMPORTS} && %{$self->{IMPORTS}}) { # Make import files (needed for static build) -d 'tmp_imp' or mkdir 'tmp_imp', 0777 or die "Can't mkdir tmp_imp"; open my $imp, '>', 'tmpimp.imp' or die "Can't open tmpimp.imp"; foreach my $name (sort keys %{$self->{IMPORTS}}) { my $exp = $self->{IMPORTS}->{$name}; my ($lib, $id) = ($exp =~ /(.*)\.(.*)/) or die "Malformed IMPORT `$exp'"; print $imp "$name $lib $id ?\n"; } close $imp or die "Can't close tmpimp.imp"; # print "emximp -o tmpimp$Config::Config{lib_ext} tmpimp.imp\n"; system "emximp -o tmpimp$Config::Config{lib_ext} tmpimp.imp" and die "Cannot make import library: $!, \$?=$?"; # May be running under miniperl, so have no glob... eval { unlink ; 1 } or system "rm tmp_imp/*"; system "cd tmp_imp; $Config::Config{ar} x ../tmpimp$Config::Config{lib_ext}" and die "Cannot extract import objects: $!, \$?=$?"; } return '' if $self->{SKIPHASH}{'dynamic'}; $self->xs_dlsyms_iterator(\%attribs); } sub xs_dlsyms_ext { '.def'; } sub xs_dlsyms_extra { join '', map { qq{, "$_" => "\$($_)"} } qw(VERSION DISTNAME INSTALLDIRS); } sub static_lib_pure_cmd { my($self) = @_; my $old = $self->SUPER::static_lib_pure_cmd; return $old unless $self->{IMPORTS} && %{$self->{IMPORTS}}; $old . <<'EOC'; $(AR) $(AR_STATIC_ARGS) "$@" tmp_imp/* $(RANLIB) "$@" EOC } sub replace_manpage_separator { my($self,$man) = @_; $man =~ s,/+,.,g; $man; } sub maybe_command { my($self,$file) = @_; $file =~ s,[/\\]+,/,g; return $file if -x $file && ! -d _; return "$file.exe" if -x "$file.exe" && ! -d _; return "$file.cmd" if -x "$file.cmd" && ! -d _; return; } sub init_linker { my $self = shift; $self->{PERL_ARCHIVE} = "\$(PERL_INC)/libperl\$(LIB_EXT)"; $self->{PERL_ARCHIVEDEP} ||= ''; $self->{PERL_ARCHIVE_AFTER} = $OS2::is_aout ? '' : '$(PERL_INC)/libperl_override$(LIB_EXT)'; $self->{EXPORT_LIST} = '$(BASEEXT).def'; } sub os_flavor { return('OS/2'); } sub xs_static_lib_is_xs { return 1; } 1; package ExtUtils::CBuilder; use File::Spec (); use File::Path (); use File::Basename (); use Perl::OSType qw/os_type/; use warnings; use strict; our $VERSION = '0.280230'; # VERSION our @ISA; # We only use this once - don't waste a symbol table entry on it. # More importantly, don't make it an inheritable method. my $load = sub { my $mod = shift; eval "use $mod"; die $@ if $@; @ISA = ($mod); }; { my @package = split /::/, __PACKAGE__; my $ostype = os_type(); if (grep {-e File::Spec->catfile($_, @package, 'Platform', $^O) . '.pm'} @INC) { $load->(__PACKAGE__ . "::Platform::$^O"); } elsif ( $ostype && grep {-e File::Spec->catfile($_, @package, 'Platform', $ostype) . '.pm'} @INC) { $load->(__PACKAGE__ . "::Platform::$ostype"); } else { $load->(__PACKAGE__ . "::Base"); } } 1; __END__ package ExtUtils::Install; use strict; use Config qw(%Config); use Cwd qw(cwd); use Exporter (); use File::Basename qw(dirname); use File::Copy; use File::Path; use File::Spec; our @ISA = ('Exporter'); our @EXPORT = ('install','uninstall','pm_to_blib', 'install_default'); our $MUST_REBOOT; our $VERSION = '2.14'; # <-- do not forget to update the POD section just above this line! $VERSION = eval $VERSION; BEGIN { *Is_VMS = $^O eq 'VMS' ? sub(){1} : sub(){0}; *Is_Win32 = $^O eq 'MSWin32' ? sub(){1} : sub(){0}; *Is_cygwin = $^O eq 'cygwin' ? sub(){1} : sub(){0}; *CanMoveAtBoot = ($^O eq 'MSWin32' || $^O eq 'cygwin') ? sub(){1} : sub(){0}; } my $Inc_uninstall_warn_handler; # install relative to here my $INSTALL_ROOT = $ENV{PERL_INSTALL_ROOT}; my $INSTALL_QUIET = $ENV{PERL_INSTALL_QUIET}; $INSTALL_QUIET = 1 if (!exists $ENV{PERL_INSTALL_QUIET} and defined $ENV{MAKEFLAGS} and $ENV{MAKEFLAGS} =~ /\b(s|silent|quiet)\b/); my $Curdir = File::Spec->curdir; sub _estr(@) { return join "\n",'!' x 72,@_,'!' x 72,''; } {my %warned; sub _warnonce(@) { my $first=shift; my $msg=_estr "WARNING: $first",@_; warn $msg unless $warned{$msg}++; }} sub _choke(@) { my $first=shift; my $msg=_estr "ERROR: $first",@_; require Carp; Carp::croak($msg); } sub _croak { require Carp; Carp::croak(@_); } sub _confess { require Carp; Carp::confess(@_); } sub _compare { require File::Compare; File::Compare::compare(@_); } sub _chmod($$;$) { my ( $mode, $item, $verbose )=@_; $verbose ||= 0; if (chmod $mode, $item) { printf "chmod(0%o, %s)\n",$mode, $item if $verbose > 1; } else { my $err="$!"; _warnonce sprintf "WARNING: Failed chmod(0%o, %s): %s\n", $mode, $item, $err if -e $item; } } { my $Has_Win32API_File; sub _move_file_at_boot { #XXX OS-SPECIFIC my ( $file, $target, $moan )= @_; _confess("Panic: Can't _move_file_at_boot on this platform!") unless CanMoveAtBoot; my $descr= ref $target ? "'$file' for deletion" : "'$file' for installation as '$target'"; # *note* CanMoveAtBoot is only incidentally the same condition as below # this needs not hold true in the future. $Has_Win32API_File = (Is_Win32 || Is_cygwin) ? (eval {require Win32API::File; 1} || 0) : 0 unless defined $Has_Win32API_File; if ( ! $Has_Win32API_File ) { my @msg=( "Cannot schedule $descr at reboot.", "Try installing Win32API::File to allow operations on locked files", "to be scheduled during reboot. Or try to perform the operation by", "hand yourself. (You may need to close other perl processes first)" ); if ( $moan ) { _warnonce(@msg) } else { _choke(@msg) } return 0; } my $opts= Win32API::File::MOVEFILE_DELAY_UNTIL_REBOOT(); $opts= $opts | Win32API::File::MOVEFILE_REPLACE_EXISTING() unless ref $target; _chmod( 0666, $file ); _chmod( 0666, $target ) unless ref $target; if (Win32API::File::MoveFileEx( $file, $target, $opts )) { $MUST_REBOOT ||= ref $target ? 0 : 1; return 1; } else { my @msg=( "MoveFileEx $descr at reboot failed: $^E", "You may try to perform the operation by hand yourself. ", "(You may need to close other perl processes first).", ); if ( $moan ) { _warnonce(@msg) } else { _choke(@msg) } } return 0; } } sub _unlink_or_rename { #XXX OS-SPECIFIC my ( $file, $tryhard, $installing )= @_; # this chmod was originally unconditional. However, its not needed on # POSIXy systems since permission to unlink a file is specified by the # directory rather than the file; and in fact it screwed up hard- and # symlinked files. Keep it for other platforms in case its still # needed there. if ($^O =~ /^(dos|os2|MSWin32|VMS)$/) { _chmod( 0666, $file ); } my $unlink_count = 0; while (unlink $file) { $unlink_count++; } return $file if $unlink_count > 0; my $error="$!"; _choke("Cannot unlink '$file': $!") unless CanMoveAtBoot && $tryhard; my $tmp= "AAA"; ++$tmp while -e "$file.$tmp"; $tmp= "$file.$tmp"; warn "WARNING: Unable to unlink '$file': $error\n", "Going to try to rename it to '$tmp'.\n"; if ( rename $file, $tmp ) { warn "Rename successful. Scheduling '$tmp'\nfor deletion at reboot.\n"; # when $installing we can set $moan to true. # IOW, if we cant delete the renamed file at reboot its # not the end of the world. The other cases are more serious # and need to be fatal. _move_file_at_boot( $tmp, [], $installing ); return $file; } elsif ( $installing ) { _warnonce("Rename failed: $!. Scheduling '$tmp'\nfor". " installation as '$file' at reboot.\n"); _move_file_at_boot( $tmp, $file ); return $tmp; } else { _choke("Rename failed:$!", "Cannot proceed."); } } sub _get_install_skip { my ( $skip, $verbose )= @_; if ($ENV{EU_INSTALL_IGNORE_SKIP}) { print "EU_INSTALL_IGNORE_SKIP is set, ignore skipfile settings\n" if $verbose>2; return []; } if ( ! defined $skip ) { print "Looking for install skip list\n" if $verbose>2; for my $file ( 'INSTALL.SKIP', $ENV{EU_INSTALL_SITE_SKIPFILE} ) { next unless $file; print "\tChecking for $file\n" if $verbose>2; if (-e $file) { $skip= $file; last; } } } if ($skip && !ref $skip) { print "Reading skip patterns from '$skip'.\n" if $verbose; if (open my $fh,$skip ) { my @patterns; while (<$fh>) { chomp; next if /^\s*(?:#|$)/; print "\tSkip pattern: $_\n" if $verbose>3; push @patterns, $_; } $skip= \@patterns; } else { warn "Can't read skip file:'$skip':$!\n"; $skip=[]; } } elsif ( UNIVERSAL::isa($skip,'ARRAY') ) { print "Using array for skip list\n" if $verbose>2; } elsif ($verbose) { print "No skip list found.\n" if $verbose>1; $skip= []; } warn "Got @{[0+@$skip]} skip patterns.\n" if $verbose>3; return $skip } { my $has_posix; sub _have_write_access { my $dir=shift; unless (defined $has_posix) { $has_posix = (!Is_cygwin && !Is_Win32 && eval { local $^W; require POSIX; 1} ) || 0; } if ($has_posix) { return POSIX::access($dir, POSIX::W_OK()); } else { return -w $dir; } } } sub _can_write_dir { my $dir=shift; return unless defined $dir and length $dir; my ($vol, $dirs, $file) = File::Spec->splitpath($dir,1); my @dirs = File::Spec->splitdir($dirs); unshift @dirs, File::Spec->curdir unless File::Spec->file_name_is_absolute($dir); my $path=''; my @make; while (@dirs) { if (Is_VMS) { $dir = File::Spec->catdir($vol,@dirs); } else { $dir = File::Spec->catdir(@dirs); $dir = File::Spec->catpath($vol,$dir,'') if defined $vol and length $vol; } next if ( $dir eq $path ); if ( ! -e $dir ) { unshift @make,$dir; next; } if ( _have_write_access($dir) ) { return 1,$dir,@make } else { return 0,$dir,@make } } continue { pop @dirs; } return 0; } sub _mkpath { my ($dir,$show,$mode,$verbose,$dry_run)=@_; if ( $verbose && $verbose > 1 && ! -d $dir) { $show= 1; printf "mkpath(%s,%d,%#o)\n", $dir, $show, $mode; } if (!$dry_run) { if ( ! eval { File::Path::mkpath($dir,$show,$mode); 1 } ) { _choke("Can't create '$dir'","$@"); } } my ($can,$root,@make)=_can_write_dir($dir); if (!$can) { my @msg=( "Can't create '$dir'", $root ? "Do not have write permissions on '$root'" : "Unknown Error" ); if ($dry_run) { _warnonce @msg; } else { _choke @msg; } } elsif ($show and $dry_run) { print "$_\n" for @make; } } sub _copy { my ( $from, $to, $verbose, $dry_run)=@_; if ($verbose && $verbose>1) { printf "copy(%s,%s)\n", $from, $to; } if (!$dry_run) { File::Copy::copy($from,$to) or _croak( _estr "ERROR: Cannot copy '$from' to '$to': $!" ); } } sub _chdir { my ($dir)= @_; my $ret; if (defined wantarray) { $ret= cwd; } chdir $dir or _choke("Couldn't chdir to '$dir': $!"); return $ret; } sub install { #XXX OS-SPECIFIC my($from_to,$verbose,$dry_run,$uninstall_shadows,$skip,$always_copy,$result) = @_; if (@_==1 and eval { 1+@$from_to }) { my %opts = @$from_to; $from_to = $opts{from_to} or _confess("from_to is a mandatory parameter"); $verbose = $opts{verbose}; $dry_run = $opts{dry_run}; $uninstall_shadows = $opts{uninstall_shadows}; $skip = $opts{skip}; $always_copy = $opts{always_copy}; $result = $opts{result}; } $result ||= {}; $verbose ||= 0; $dry_run ||= 0; $skip= _get_install_skip($skip,$verbose); $always_copy = $ENV{EU_INSTALL_ALWAYS_COPY} || $ENV{EU_ALWAYS_COPY} || 0 unless defined $always_copy; my(%from_to) = %$from_to; my(%pack, $dir, %warned); require ExtUtils::Packlist; my($packlist) = ExtUtils::Packlist->new(); local(*DIR); for (qw/read write/) { $pack{$_}=$from_to{$_}; delete $from_to{$_}; } my $tmpfile = install_rooted_file($pack{"read"}); $packlist->read($tmpfile) if (-f $tmpfile); my $cwd = cwd(); my @found_files; my %check_dirs; require File::Find; my $blib_lib = File::Spec->catdir('blib', 'lib'); my $blib_arch = File::Spec->catdir('blib', 'arch'); # File::Find seems to always be Unixy except on MacPerl :( my $current_directory = $^O eq 'MacOS' ? $Curdir : '.'; MOD_INSTALL: foreach my $source (sort keys %from_to) { #copy the tree to the target directory without altering #timestamp and permission and remember for the .packlist #file. The packlist file contains the absolute paths of the #install locations. AFS users may call this a bug. We'll have #to reconsider how to add the means to satisfy AFS users also. #October 1997: we want to install .pm files into archlib if #there are any files in arch. So we depend on having ./blib/arch #hardcoded here. my $targetroot = install_rooted_dir($from_to{$source}); if ($source eq $blib_lib and exists $from_to{$blib_arch} and directory_not_empty($blib_arch) ){ $targetroot = install_rooted_dir($from_to{$blib_arch}); print "Files found in $blib_arch: installing files in $blib_lib into architecture dependent library tree\n"; } next unless -d $source; _chdir($source); # 5.5.3's File::Find missing no_chdir option # XXX OS-SPECIFIC File::Find::find(sub { my ($mode,$size,$atime,$mtime) = (stat)[2,7,8,9]; return if !-f _; my $origfile = $_; return if $origfile eq ".exists"; my $targetdir = File::Spec->catdir($targetroot, $File::Find::dir); my $targetfile = File::Spec->catfile($targetdir, $origfile); my $sourcedir = File::Spec->catdir($source, $File::Find::dir); my $sourcefile = File::Spec->catfile($sourcedir, $origfile); for my $pat (@$skip) { if ( $sourcefile=~/$pat/ ) { print "Skipping $targetfile (filtered)\n" if $verbose>1; $result->{install_filtered}{$sourcefile} = $pat; return; } } # we have to do this for back compat with old File::Finds # and because the target is relative my $save_cwd = _chdir($cwd); my $diff = 0; # XXX: I wonder how useful this logic is actually -- demerphq if ( $always_copy or !-f $targetfile or -s $targetfile != $size) { $diff++; } else { # we might not need to copy this file $diff = _compare($sourcefile, $targetfile); } $check_dirs{$targetdir}++ unless -w $targetfile; push @found_files, [ $diff, $File::Find::dir, $origfile, $mode, $size, $atime, $mtime, $targetdir, $targetfile, $sourcedir, $sourcefile, ]; #restore the original directory we were in when File::Find #called us so that it doesn't get horribly confused. _chdir($save_cwd); }, $current_directory ); _chdir($cwd); } foreach my $targetdir (sort keys %check_dirs) { _mkpath( $targetdir, 0, 0755, $verbose, $dry_run ); } foreach my $found (@found_files) { my ($diff, $ffd, $origfile, $mode, $size, $atime, $mtime, $targetdir, $targetfile, $sourcedir, $sourcefile)= @$found; my $realtarget= $targetfile; if ($diff) { eval { if (-f $targetfile) { print "_unlink_or_rename($targetfile)\n" if $verbose>1; $targetfile= _unlink_or_rename( $targetfile, 'tryhard', 'install' ) unless $dry_run; } elsif ( ! -d $targetdir ) { _mkpath( $targetdir, 0, 0755, $verbose, $dry_run ); } print "Installing $targetfile\n"; _copy( $sourcefile, $targetfile, $verbose, $dry_run, ); #XXX OS-SPECIFIC print "utime($atime,$mtime,$targetfile)\n" if $verbose>1; utime($atime,$mtime + Is_VMS,$targetfile) unless $dry_run>1; $mode = 0444 | ( $mode & 0111 ? 0111 : 0 ); $mode = $mode | 0222 if $realtarget ne $targetfile; _chmod( $mode, $targetfile, $verbose ); $result->{install}{$targetfile} = $sourcefile; 1 } or do { $result->{install_fail}{$targetfile} = $sourcefile; die $@; }; } else { $result->{install_unchanged}{$targetfile} = $sourcefile; print "Skipping $targetfile (unchanged)\n" if $verbose; } if ( $uninstall_shadows ) { inc_uninstall($sourcefile,$ffd, $verbose, $dry_run, $realtarget ne $targetfile ? $realtarget : "", $result); } # Record the full pathname. $packlist->{$targetfile}++; } if ($pack{'write'}) { $dir = install_rooted_dir(dirname($pack{'write'})); _mkpath( $dir, 0, 0755, $verbose, $dry_run ); print "Writing $pack{'write'}\n" if $verbose; $packlist->write(install_rooted_file($pack{'write'})) unless $dry_run; } _do_cleanup($verbose); return $result; } sub _do_cleanup { my ($verbose) = @_; if ($MUST_REBOOT) { die _estr "Operation not completed! ", "You must reboot to complete the installation.", "Sorry."; } elsif (defined $MUST_REBOOT & $verbose) { warn _estr "Installation will be completed at the next reboot.\n", "However it is not necessary to reboot immediately.\n"; } } sub install_rooted_file { if (defined $INSTALL_ROOT) { File::Spec->catfile($INSTALL_ROOT, $_[0]); } else { $_[0]; } } sub install_rooted_dir { if (defined $INSTALL_ROOT) { File::Spec->catdir($INSTALL_ROOT, $_[0]); } else { $_[0]; } } sub forceunlink { my ( $file, $tryhard )= @_; #XXX OS-SPECIFIC _unlink_or_rename( $file, $tryhard, not("installing") ); } sub directory_not_empty ($) { my($dir) = @_; my $files = 0; require File::Find; File::Find::find(sub { return if $_ eq ".exists"; if (-f) { $File::Find::prune++; $files = 1; } }, $dir); return $files; } sub install_default { @_ < 2 or _croak("install_default should be called with 0 or 1 argument"); my $FULLEXT = @_ ? shift : $ARGV[0]; defined $FULLEXT or die "Do not know to where to write install log"; my $INST_LIB = File::Spec->catdir($Curdir,"blib","lib"); my $INST_ARCHLIB = File::Spec->catdir($Curdir,"blib","arch"); my $INST_BIN = File::Spec->catdir($Curdir,'blib','bin'); my $INST_SCRIPT = File::Spec->catdir($Curdir,'blib','script'); my $INST_MAN1DIR = File::Spec->catdir($Curdir,'blib','man1'); my $INST_MAN3DIR = File::Spec->catdir($Curdir,'blib','man3'); my @INST_HTML; if($Config{installhtmldir}) { my $INST_HTMLDIR = File::Spec->catdir($Curdir,'blib','html'); @INST_HTML = ($INST_HTMLDIR => $Config{installhtmldir}); } install({ read => "$Config{sitearchexp}/auto/$FULLEXT/.packlist", write => "$Config{installsitearch}/auto/$FULLEXT/.packlist", $INST_LIB => (directory_not_empty($INST_ARCHLIB)) ? $Config{installsitearch} : $Config{installsitelib}, $INST_ARCHLIB => $Config{installsitearch}, $INST_BIN => $Config{installbin} , $INST_SCRIPT => $Config{installscript}, $INST_MAN1DIR => $Config{installman1dir}, $INST_MAN3DIR => $Config{installman3dir}, @INST_HTML, },1,0,0); } sub uninstall { my($fil,$verbose,$dry_run) = @_; $verbose ||= 0; $dry_run ||= 0; die _estr "ERROR: no packlist file found: '$fil'" unless -f $fil; # my $my_req = $self->catfile(qw(auto ExtUtils Install forceunlink.al)); # require $my_req; # Hairy, but for the first require ExtUtils::Packlist; my ($packlist) = ExtUtils::Packlist->new($fil); foreach (sort(keys(%$packlist))) { chomp; print "unlink $_\n" if $verbose; forceunlink($_,'tryhard') unless $dry_run; } print "unlink $fil\n" if $verbose; forceunlink($fil, 'tryhard') unless $dry_run; _do_cleanup($verbose); } sub inc_uninstall { my($filepath,$libdir,$verbose,$dry_run,$ignore,$results) = @_; my($dir); $ignore||=""; my $file = (File::Spec->splitpath($filepath))[2]; my %seen_dir = (); my @PERL_ENV_LIB = split $Config{path_sep}, defined $ENV{'PERL5LIB'} ? $ENV{'PERL5LIB'} : $ENV{'PERLLIB'} || ''; my @dirs=( @PERL_ENV_LIB, @INC, @Config{qw(archlibexp privlibexp sitearchexp sitelibexp)}); #warn join "\n","---",@dirs,"---"; my $seen_ours; foreach $dir ( @dirs ) { my $canonpath = Is_VMS ? $dir : File::Spec->canonpath($dir); next if $canonpath eq $Curdir; next if $seen_dir{$canonpath}++; my $targetfile = File::Spec->catfile($canonpath,$libdir,$file); next unless -f $targetfile; # The reason why we compare file's contents is, that we cannot # know, which is the file we just installed (AFS). So we leave # an identical file in place my $diff = 0; if ( -f $targetfile && -s _ == -s $filepath) { # We have a good chance, we can skip this one $diff = _compare($filepath,$targetfile); } else { $diff++; } print "#$file and $targetfile differ\n" if $diff && $verbose > 1; if (!$diff or $targetfile eq $ignore) { $seen_ours = 1; next; } if ($dry_run) { $results->{uninstall}{$targetfile} = $filepath; if ($verbose) { $Inc_uninstall_warn_handler ||= ExtUtils::Install::Warn->new(); $libdir =~ s|^\./||s ; # That's just cosmetics, no need to port. It looks prettier. $Inc_uninstall_warn_handler->add( File::Spec->catfile($libdir, $file), $targetfile ); } # if not verbose, we just say nothing } else { print "Unlinking $targetfile (shadowing?)\n" if $verbose; eval { die "Fake die for testing" if $ExtUtils::Install::Testing and ucase(File::Spec->canonpath($ExtUtils::Install::Testing)) eq ucase($targetfile); forceunlink($targetfile,'tryhard'); $results->{uninstall}{$targetfile} = $filepath; 1; } or do { $results->{fail_uninstall}{$targetfile} = $filepath; if ($seen_ours) { warn "Failed to remove probably harmless shadow file '$targetfile'\n"; } else { die "$@\n"; } }; } } } sub run_filter { my ($cmd, $src, $dest) = @_; local(*CMD, *SRC); open(CMD, "|$cmd >$dest") || die "Cannot fork: $!"; open(SRC, $src) || die "Cannot open $src: $!"; my $buf; my $sz = 1024; while (my $len = sysread(SRC, $buf, $sz)) { syswrite(CMD, $buf, $len); } close SRC; close CMD or die "Filter command '$cmd' failed for $src"; } sub pm_to_blib { my($fromto,$autodir,$pm_filter) = @_; _mkpath($autodir,0,0755) if defined $autodir; while(my($from, $to) = each %$fromto) { if( -f $to && -s $from == -s $to && -M $to < -M $from ) { print "Skip $to (unchanged)\n" unless $INSTALL_QUIET; next; } # When a pm_filter is defined, we need to pre-process the source first # to determine whether it has changed or not. Therefore, only perform # the comparison check when there's no filter to be ran. # -- RAM, 03/01/2001 my $need_filtering = defined $pm_filter && length $pm_filter && $from =~ /\.pm$/; if (!$need_filtering && 0 == _compare($from,$to)) { print "Skip $to (unchanged)\n" unless $INSTALL_QUIET; next; } if (-f $to){ # we wont try hard here. its too likely to mess things up. forceunlink($to); } else { _mkpath(dirname($to),0,0755); } if ($need_filtering) { run_filter($pm_filter, $from, $to); print "$pm_filter <$from >$to\n"; } else { _copy( $from, $to ); print "cp $from $to\n" unless $INSTALL_QUIET; } my($mode,$atime,$mtime) = (stat $from)[2,8,9]; utime($atime,$mtime+Is_VMS,$to); _chmod(0444 | ( $mode & 0111 ? 0111 : 0 ),$to); next unless $from =~ /\.pm$/; _autosplit($to,$autodir) if defined $autodir; } } sub _autosplit { #XXX OS-SPECIFIC require AutoSplit; my $retval = AutoSplit::autosplit(@_); close *AutoSplit::IN if defined *AutoSplit::IN{IO}; return $retval; } package ExtUtils::Install::Warn; sub new { bless {}, shift } sub add { my($self,$file,$targetfile) = @_; push @{$self->{$file}}, $targetfile; } sub DESTROY { unless(defined $INSTALL_ROOT) { my $self = shift; my($file,$i,$plural); foreach $file (sort keys %$self) { $plural = @{$self->{$file}} > 1 ? "s" : ""; print "## Differing version$plural of $file found. You might like to\n"; for (0..$#{$self->{$file}}) { print "rm ", $self->{$file}[$_], "\n"; $i++; } } $plural = $i>1 ? "all those files" : "this file"; my $inst = (_invokant() eq 'ExtUtils::MakeMaker') ? ( $Config::Config{make} || 'make' ).' install' . ( ExtUtils::Install::Is_VMS ? '/MACRO="UNINST"=1' : ' UNINST=1' ) : './Build install uninst=1'; print "## Running '$inst' will unlink $plural for you.\n"; } } sub _invokant { my @stack; my $frame = 0; while (my $file = (caller($frame++))[1]) { push @stack, (File::Spec->splitpath($file))[2]; } my $builder; my $top = pop @stack; if ($top =~ /^Build/i || exists($INC{'Module/Build.pm'})) { $builder = 'Module::Build'; } else { $builder = 'ExtUtils::MakeMaker'; } return $builder; } 1; package ExtUtils::MM_Cygwin; use strict; use ExtUtils::MakeMaker::Config; use File::Spec; require ExtUtils::MM_Unix; require ExtUtils::MM_Win32; our @ISA = qw( ExtUtils::MM_Unix ); our $VERSION = '7.34'; $VERSION = eval $VERSION; sub os_flavor { return('Unix', 'Cygwin'); } sub cflags { my($self,$libperl)=@_; return $self->{CFLAGS} if $self->{CFLAGS}; return '' unless $self->needs_linking(); my $base = $self->SUPER::cflags($libperl); foreach (split /\n/, $base) { /^(\S*)\s*=\s*(\S*)$/ and $self->{$1} = $2; }; $self->{CCFLAGS} .= " -DUSEIMPORTLIB" if ($Config{useshrplib} eq 'true'); return $self->{CFLAGS} = qq{ CCFLAGS = $self->{CCFLAGS} OPTIMIZE = $self->{OPTIMIZE} PERLTYPE = $self->{PERLTYPE} }; } sub replace_manpage_separator { my($self, $man) = @_; $man =~ s{/+}{.}g; return $man; } sub init_linker { my $self = shift; if ($Config{useshrplib} eq 'true') { my $libperl = '$(PERL_INC)' .'/'. "$Config{libperl}"; if( $] >= 5.006002 ) { $libperl =~ s/(dll\.)?a$/dll.a/; } $self->{PERL_ARCHIVE} = $libperl; } else { $self->{PERL_ARCHIVE} = '$(PERL_INC)' .'/'. ("$Config{libperl}" or "libperl.a"); } $self->{PERL_ARCHIVEDEP} ||= ''; $self->{PERL_ARCHIVE_AFTER} ||= ''; $self->{EXPORT_LIST} ||= ''; } sub maybe_command { my ($self, $file) = @_; my $cygpath = Cygwin::posix_to_win_path('/', 1); my $filepath = Cygwin::posix_to_win_path($file, 1); return (substr($filepath,0,length($cygpath)) eq $cygpath) ? $self->SUPER::maybe_command($file) # Unix : ExtUtils::MM_Win32->maybe_command($file); # Win32 } sub dynamic_lib { my($self, %attribs) = @_; my $s = ExtUtils::MM_Unix::dynamic_lib($self, %attribs); return '' unless $s; return $s unless %{$self->{XS}}; # do an ephemeral rebase so the new DLL fits to the current rebase map $s .= "\t/bin/find \$\(INST_ARCHLIB\)/auto -xdev -name \\*.$self->{DLEXT} | /bin/rebase -sOT -" if (( $Config{myarchname} eq 'i686-cygwin' ) and not ( exists $ENV{CYGPORT_PACKAGE_VERSION} )); $s; } sub install { my($self, %attribs) = @_; my $s = ExtUtils::MM_Unix::install($self, %attribs); return '' unless $s; return $s unless %{$self->{XS}}; my $INSTALLDIRS = $self->{INSTALLDIRS}; my $INSTALLLIB = $self->{"INSTALL". ($INSTALLDIRS eq 'perl' ? 'ARCHLIB' : uc($INSTALLDIRS)."ARCH")}; my $dop = "\$\(DESTDIR\)$INSTALLLIB/auto/"; my $dll = "$dop/$self->{FULLEXT}/$self->{BASEEXT}.$self->{DLEXT}"; $s =~ s|^(pure_install :: pure_\$\(INSTALLDIRS\)_install\n\t)\$\(NOECHO\) \$\(NOOP\)\n|$1\$(CHMOD) \$(PERM_RWX) $dll\n\t/bin/find $dop -xdev -name \\*.$self->{DLEXT} \| /bin/rebase -sOT -\n|m if (( $Config{myarchname} eq 'i686-cygwin') and not ( exists $ENV{CYGPORT_PACKAGE_VERSION} )); $s; } sub all_target { ExtUtils::MM_Unix::all_target(shift); } 1; package ExtUtils::MM_BeOS; use strict; use ExtUtils::MakeMaker::Config; use File::Spec; require ExtUtils::MM_Any; require ExtUtils::MM_Unix; our @ISA = qw( ExtUtils::MM_Any ExtUtils::MM_Unix ); our $VERSION = '7.34'; $VERSION = eval $VERSION; sub os_flavor { return('BeOS'); } sub init_linker { my($self) = shift; $self->{PERL_ARCHIVE} ||= File::Spec->catdir('$(PERL_INC)',$Config{libperl}); $self->{PERL_ARCHIVEDEP} ||= ''; $self->{PERL_ARCHIVE_AFTER} ||= ''; $self->{EXPORT_LIST} ||= ''; } package ExtUtils::Packlist; use 5.00503; use strict; use Carp qw(); use Config; use vars qw($VERSION $Relocations); $VERSION = '2.14'; $VERSION = eval $VERSION; # Used for generating filehandle globs. IO::File might not be available! my $fhname = "FH1"; sub mkfh() { no strict; local $^W; my $fh = \*{$fhname++}; use strict; return($fh); } sub __find_relocations { my %paths; while (my ($raw_key, $raw_val) = each %Config) { my $exp_key = $raw_key . "exp"; next unless exists $Config{$exp_key}; next unless $raw_val =~ m!\.\.\./!; $paths{$Config{$exp_key}}++; } # Longest prefixes go first in the alternatives my $alternations = join "|", map {quotemeta $_} sort {length $b <=> length $a} keys %paths; qr/^($alternations)/o; } sub new($$) { my ($class, $packfile) = @_; $class = ref($class) || $class; my %self; tie(%self, $class, $packfile); return(bless(\%self, $class)); } sub TIEHASH { my ($class, $packfile) = @_; my $self = { packfile => $packfile }; bless($self, $class); $self->read($packfile) if (defined($packfile) && -f $packfile); return($self); } sub STORE { $_[0]->{data}->{$_[1]} = $_[2]; } sub FETCH { return($_[0]->{data}->{$_[1]}); } sub FIRSTKEY { my $reset = scalar(keys(%{$_[0]->{data}})); return(each(%{$_[0]->{data}})); } sub NEXTKEY { return(each(%{$_[0]->{data}})); } sub EXISTS { return(exists($_[0]->{data}->{$_[1]})); } sub DELETE { return(delete($_[0]->{data}->{$_[1]})); } sub CLEAR { %{$_[0]->{data}} = (); } sub DESTROY { } sub read($;$) { my ($self, $packfile) = @_; $self = tied(%$self) || $self; if (defined($packfile)) { $self->{packfile} = $packfile; } else { $packfile = $self->{packfile}; } Carp::croak("No packlist filename specified") if (! defined($packfile)); my $fh = mkfh(); open($fh, "<$packfile") || Carp::croak("Can't open file $packfile: $!"); $self->{data} = {}; my ($line); while (defined($line = <$fh>)) { chomp $line; my ($key, $data) = $line; if ($key =~ /^(.*?)( \w+=.*)$/) { $key = $1; $data = { map { split('=', $_) } split(' ', $2)}; if ($Config{userelocatableinc} && $data->{relocate_as}) { require File::Spec; require Cwd; my ($vol, $dir) = File::Spec->splitpath($packfile); my $newpath = File::Spec->catpath($vol, $dir, $data->{relocate_as}); $key = Cwd::realpath($newpath); } } $key =~ s!/\./!/!g; # Some .packlists have spurious '/./' bits in the paths $self->{data}->{$key} = $data; } close($fh); } sub write($;$) { my ($self, $packfile) = @_; $self = tied(%$self) || $self; if (defined($packfile)) { $self->{packfile} = $packfile; } else { $packfile = $self->{packfile}; } Carp::croak("No packlist filename specified") if (! defined($packfile)); my $fh = mkfh(); open($fh, ">$packfile") || Carp::croak("Can't open file $packfile: $!"); foreach my $key (sort(keys(%{$self->{data}}))) { my $data = $self->{data}->{$key}; if ($Config{userelocatableinc}) { $Relocations ||= __find_relocations(); if ($packfile =~ $Relocations) { # We are writing into a subdirectory of a run-time relocated # path. Figure out if the this file is also within a subdir. my $prefix = $1; if (File::Spec->no_upwards(File::Spec->abs2rel($key, $prefix))) { # The relocated path is within the found prefix my $packfile_prefix; (undef, $packfile_prefix) = File::Spec->splitpath($packfile); my $relocate_as = File::Spec->abs2rel($key, $packfile_prefix); if (!ref $data) { $data = {}; } $data->{relocate_as} = $relocate_as; } } } print $fh ("$key"); if (ref($data)) { foreach my $k (sort(keys(%$data))) { print $fh (" $k=$data->{$k}"); } } print $fh ("\n"); } close($fh); } sub validate($;$) { my ($self, $remove) = @_; $self = tied(%$self) || $self; my @missing; foreach my $key (sort(keys(%{$self->{data}}))) { if (! -e $key) { push(@missing, $key); delete($self->{data}{$key}) if ($remove); } } return(@missing); } sub packlist_file($) { my ($self) = @_; $self = tied(%$self) || $self; return($self->{packfile}); } 1; __END__ package ExtUtils::MY; use strict; require ExtUtils::MM; our $VERSION = '7.34'; $VERSION = eval $VERSION; our @ISA = qw(ExtUtils::MM); { package MY; our @ISA = qw(ExtUtils::MY); } sub DESTROY {} package ExtUtils::Typemaps; use 5.006001; use strict; use warnings; our $VERSION = '3.38'; require ExtUtils::ParseXS; require ExtUtils::ParseXS::Constants; require ExtUtils::Typemaps::InputMap; require ExtUtils::Typemaps::OutputMap; require ExtUtils::Typemaps::Type; sub new { my $class = shift; my %args = @_; if (defined $args{file} and defined $args{string}) { die("Cannot handle both 'file' and 'string' arguments to constructor"); } my $self = bless { file => undef, %args, typemap_section => [], typemap_lookup => {}, input_section => [], input_lookup => {}, output_section => [], output_lookup => {}, } => $class; $self->_init(); return $self; } sub _init { my $self = shift; if (defined $self->{string}) { $self->_parse(\($self->{string}), $self->{lineno_offset}, $self->{fake_filename}); delete $self->{string}; } elsif (defined $self->{file} and -e $self->{file}) { open my $fh, '<', $self->{file} or die "Cannot open typemap file '" . $self->{file} . "' for reading: $!"; local $/ = undef; my $string = <$fh>; $self->_parse(\$string, $self->{lineno_offset}, $self->{file}); } } sub file { $_[0]->{file} = $_[1] if @_ > 1; $_[0]->{file} } sub add_typemap { my $self = shift; my $type; my %args; if ((@_ % 2) == 1) { my $orig = shift; $type = $orig->new(); %args = @_; } else { %args = @_; my $ctype = $args{ctype}; die("Need ctype argument") if not defined $ctype; my $xstype = $args{xstype}; die("Need xstype argument") if not defined $xstype; $type = ExtUtils::Typemaps::Type->new( xstype => $xstype, 'prototype' => $args{'prototype'}, ctype => $ctype, ); } if ($args{skip} and $args{replace}) { die("Cannot use both 'skip' and 'replace'"); } if ($args{replace}) { $self->remove_typemap(ctype => $type->ctype); } elsif ($args{skip}) { return() if exists $self->{typemap_lookup}{$type->ctype}; } else { $self->validate(typemap_xstype => $type->xstype, ctype => $type->ctype); } # store push @{$self->{typemap_section}}, $type; # remember type for lookup, too. $self->{typemap_lookup}{$type->tidy_ctype} = $#{$self->{typemap_section}}; return 1; } sub add_inputmap { my $self = shift; my $input; my %args; if ((@_ % 2) == 1) { my $orig = shift; $input = $orig->new(); %args = @_; } else { %args = @_; my $xstype = $args{xstype}; die("Need xstype argument") if not defined $xstype; my $code = $args{code}; die("Need code argument") if not defined $code; $input = ExtUtils::Typemaps::InputMap->new( xstype => $xstype, code => $code, ); } if ($args{skip} and $args{replace}) { die("Cannot use both 'skip' and 'replace'"); } if ($args{replace}) { $self->remove_inputmap(xstype => $input->xstype); } elsif ($args{skip}) { return() if exists $self->{input_lookup}{$input->xstype}; } else { $self->validate(inputmap_xstype => $input->xstype); } # store push @{$self->{input_section}}, $input; # remember type for lookup, too. $self->{input_lookup}{$input->xstype} = $#{$self->{input_section}}; return 1; } sub add_outputmap { my $self = shift; my $output; my %args; if ((@_ % 2) == 1) { my $orig = shift; $output = $orig->new(); %args = @_; } else { %args = @_; my $xstype = $args{xstype}; die("Need xstype argument") if not defined $xstype; my $code = $args{code}; die("Need code argument") if not defined $code; $output = ExtUtils::Typemaps::OutputMap->new( xstype => $xstype, code => $code, ); } if ($args{skip} and $args{replace}) { die("Cannot use both 'skip' and 'replace'"); } if ($args{replace}) { $self->remove_outputmap(xstype => $output->xstype); } elsif ($args{skip}) { return() if exists $self->{output_lookup}{$output->xstype}; } else { $self->validate(outputmap_xstype => $output->xstype); } # store push @{$self->{output_section}}, $output; # remember type for lookup, too. $self->{output_lookup}{$output->xstype} = $#{$self->{output_section}}; return 1; } sub add_string { my $self = shift; my %args = @_; die("Need 'string' argument") if not defined $args{string}; # no, this is not elegant. my $other = ExtUtils::Typemaps->new(string => $args{string}); $self->merge(typemap => $other); } sub remove_typemap { my $self = shift; my $ctype; if (@_ > 1) { my %args = @_; $ctype = $args{ctype}; die("Need ctype argument") if not defined $ctype; $ctype = tidy_type($ctype); } else { $ctype = $_[0]->tidy_ctype; } return $self->_remove($ctype, $self->{typemap_section}, $self->{typemap_lookup}); } sub remove_inputmap { my $self = shift; my $xstype; if (@_ > 1) { my %args = @_; $xstype = $args{xstype}; die("Need xstype argument") if not defined $xstype; } else { $xstype = $_[0]->xstype; } return $self->_remove($xstype, $self->{input_section}, $self->{input_lookup}); } sub remove_outputmap { my $self = shift; my $xstype; if (@_ > 1) { my %args = @_; $xstype = $args{xstype}; die("Need xstype argument") if not defined $xstype; } else { $xstype = $_[0]->xstype; } return $self->_remove($xstype, $self->{output_section}, $self->{output_lookup}); } sub _remove { my $self = shift; my $rm = shift; my $array = shift; my $lookup = shift; # Just fetch the index of the item from the lookup table my $index = $lookup->{$rm}; return() if not defined $index; # Nuke the item from storage splice(@$array, $index, 1); # Decrement the storage position of all items thereafter foreach my $key (keys %$lookup) { if ($lookup->{$key} > $index) { $lookup->{$key}--; } } return(); } sub get_typemap { my $self = shift; die("Need named parameters, got uneven number") if @_ % 2; my %args = @_; my $ctype = $args{ctype}; die("Need ctype argument") if not defined $ctype; $ctype = tidy_type($ctype); my $index = $self->{typemap_lookup}{$ctype}; return() if not defined $index; return $self->{typemap_section}[$index]; } sub get_inputmap { my $self = shift; die("Need named parameters, got uneven number") if @_ % 2; my %args = @_; my $xstype = $args{xstype}; my $ctype = $args{ctype}; die("Need xstype or ctype argument") if not defined $xstype and not defined $ctype; die("Need xstype OR ctype arguments, not both") if defined $xstype and defined $ctype; if (defined $ctype) { my $tm = $self->get_typemap(ctype => $ctype); $xstype = $tm && $tm->xstype; return() if not defined $xstype; } my $index = $self->{input_lookup}{$xstype}; return() if not defined $index; return $self->{input_section}[$index]; } sub get_outputmap { my $self = shift; die("Need named parameters, got uneven number") if @_ % 2; my %args = @_; my $xstype = $args{xstype}; my $ctype = $args{ctype}; die("Need xstype or ctype argument") if not defined $xstype and not defined $ctype; die("Need xstype OR ctype arguments, not both") if defined $xstype and defined $ctype; if (defined $ctype) { my $tm = $self->get_typemap(ctype => $ctype); $xstype = $tm && $tm->xstype; return() if not defined $xstype; } my $index = $self->{output_lookup}{$xstype}; return() if not defined $index; return $self->{output_section}[$index]; } sub write { my $self = shift; my %args = @_; my $file = defined $args{file} ? $args{file} : $self->file(); die("write() needs a file argument (or set the file name of the typemap using the 'file' method)") if not defined $file; open my $fh, '>', $file or die "Cannot open typemap file '$file' for writing: $!"; print $fh $self->as_string(); close $fh; } sub as_string { my $self = shift; my $typemap = $self->{typemap_section}; my @code; push @code, "TYPEMAP\n"; foreach my $entry (@$typemap) { # type kind proto # /^(.*?\S)\s+(\S+)\s*($ExtUtils::ParseXS::Constants::PrototypeRegexp*)$/o push @code, $entry->ctype . "\t" . $entry->xstype . ($entry->proto ne '' ? "\t".$entry->proto : '') . "\n"; } my $input = $self->{input_section}; if (@$input) { push @code, "\nINPUT\n"; foreach my $entry (@$input) { push @code, $entry->xstype, "\n", $entry->code, "\n"; } } my $output = $self->{output_section}; if (@$output) { push @code, "\nOUTPUT\n"; foreach my $entry (@$output) { push @code, $entry->xstype, "\n", $entry->code, "\n"; } } return join '', @code; } sub as_embedded_typemap { my $self = shift; my $string = $self->as_string; my @ident_cand = qw(END_TYPEMAP END_OF_TYPEMAP END); my $icand = 0; my $cand_suffix = ""; while ($string =~ /^\Q$ident_cand[$icand]$cand_suffix\E\s*$/m) { $icand++; if ($icand == @ident_cand) { $icand = 0; ++$cand_suffix; } } my $marker = "$ident_cand[$icand]$cand_suffix"; return "TYPEMAP: <<$marker;\n$string\n$marker\n"; } sub merge { my $self = shift; my %args = @_; if (exists $args{typemap} and exists $args{file}) { die("Need {file} OR {typemap} argument. Not both!"); } elsif (not exists $args{typemap} and not exists $args{file}) { die("Need {file} or {typemap} argument!"); } my @params; push @params, 'replace' => $args{replace} if exists $args{replace}; push @params, 'skip' => $args{skip} if exists $args{skip}; my $typemap = $args{typemap}; if (not defined $typemap) { $typemap = ref($self)->new(file => $args{file}, @params); } # FIXME breaking encapsulation. Add accessor code. foreach my $entry (@{$typemap->{typemap_section}}) { $self->add_typemap( $entry, @params ); } foreach my $entry (@{$typemap->{input_section}}) { $self->add_inputmap( $entry, @params ); } foreach my $entry (@{$typemap->{output_section}}) { $self->add_outputmap( $entry, @params ); } return 1; } sub is_empty { my $self = shift; return @{ $self->{typemap_section} } == 0 && @{ $self->{input_section} } == 0 && @{ $self->{output_section} } == 0; } sub list_mapped_ctypes { my $self = shift; return sort keys %{ $self->{typemap_lookup} }; } sub _get_typemap_hash { my $self = shift; my $lookup = $self->{typemap_lookup}; my $storage = $self->{typemap_section}; my %rv; foreach my $ctype (keys %$lookup) { $rv{$ctype} = $storage->[ $lookup->{$ctype} ]->xstype; } return \%rv; } sub _get_inputmap_hash { my $self = shift; my $lookup = $self->{input_lookup}; my $storage = $self->{input_section}; my %rv; foreach my $xstype (keys %$lookup) { $rv{$xstype} = $storage->[ $lookup->{$xstype} ]->code; # Squash trailing whitespace to one line break # This isn't strictly necessary, but makes the output more similar # to the original ExtUtils::ParseXS. $rv{$xstype} =~ s/\s*\z/\n/; } return \%rv; } sub _get_outputmap_hash { my $self = shift; my $lookup = $self->{output_lookup}; my $storage = $self->{output_section}; my %rv; foreach my $xstype (keys %$lookup) { $rv{$xstype} = $storage->[ $lookup->{$xstype} ]->code; # Squash trailing whitespace to one line break # This isn't strictly necessary, but makes the output more similar # to the original ExtUtils::ParseXS. $rv{$xstype} =~ s/\s*\z/\n/; } return \%rv; } sub _get_prototype_hash { my $self = shift; my $lookup = $self->{typemap_lookup}; my $storage = $self->{typemap_section}; my %rv; foreach my $ctype (keys %$lookup) { $rv{$ctype} = $storage->[ $lookup->{$ctype} ]->proto || '$'; } return \%rv; } # make sure that the provided types wouldn't collide with what's # in the object already. sub validate { my $self = shift; my %args = @_; if ( exists $args{ctype} and exists $self->{typemap_lookup}{tidy_type($args{ctype})} ) { die("Multiple definition of ctype '$args{ctype}' in TYPEMAP section"); } if ( exists $args{inputmap_xstype} and exists $self->{input_lookup}{$args{inputmap_xstype}} ) { die("Multiple definition of xstype '$args{inputmap_xstype}' in INPUTMAP section"); } if ( exists $args{outputmap_xstype} and exists $self->{output_lookup}{$args{outputmap_xstype}} ) { die("Multiple definition of xstype '$args{outputmap_xstype}' in OUTPUTMAP section"); } return 1; } sub clone { my $proto = shift; my %args = @_; my $self; if ($args{shallow}) { $self = bless( { %$proto, typemap_section => [@{$proto->{typemap_section}}], typemap_lookup => {%{$proto->{typemap_lookup}}}, input_section => [@{$proto->{input_section}}], input_lookup => {%{$proto->{input_lookup}}}, output_section => [@{$proto->{output_section}}], output_lookup => {%{$proto->{output_lookup}}}, } => ref($proto) ); } else { $self = bless( { %$proto, typemap_section => [map $_->new, @{$proto->{typemap_section}}], typemap_lookup => {%{$proto->{typemap_lookup}}}, input_section => [map $_->new, @{$proto->{input_section}}], input_lookup => {%{$proto->{input_lookup}}}, output_section => [map $_->new, @{$proto->{output_section}}], output_lookup => {%{$proto->{output_lookup}}}, } => ref($proto) ); } return $self; } sub tidy_type { local $_ = shift; # for templated C++ types, do some bit of flawed canonicalization # wrt. templates at least if (/[<>]/) { s/\s*([<>])\s*/$1/g; s/>>/> >/g; } # rationalise any '*' by joining them into bunches and removing whitespace s#\s*(\*+)\s*#$1#g; s#(\*+)# $1 #g ; # trim leading & trailing whitespace s/^\s+//; s/\s+$//; # change multiple whitespace into a single space s/\s+/ /g; $_; } sub _parse { my $self = shift; my $stringref = shift; my $lineno_offset = shift; $lineno_offset = 0 if not defined $lineno_offset; my $filename = shift; $filename = '' if not defined $filename; my $replace = $self->{replace}; my $skip = $self->{skip}; die "Can only replace OR skip" if $replace and $skip; my @add_params; push @add_params, replace => 1 if $replace; push @add_params, skip => 1 if $skip; # TODO comments should round-trip, currently ignoring # TODO order of sections, multiple sections of same type # Heavily influenced by ExtUtils::ParseXS my $section = 'typemap'; my $lineno = $lineno_offset; my $junk = ""; my $current = \$junk; my @input_expr; my @output_expr; while ($$stringref =~ /^(.*)$/gcm) { local $_ = $1; ++$lineno; chomp; next if /^\s*#/; if (/^INPUT\s*$/) { $section = 'input'; $current = \$junk; next; } elsif (/^OUTPUT\s*$/) { $section = 'output'; $current = \$junk; next; } elsif (/^TYPEMAP\s*$/) { $section = 'typemap'; $current = \$junk; next; } if ($section eq 'typemap') { my $line = $_; s/^\s+//; s/\s+$//; next if $_ eq '' or /^#/; my($type, $kind, $proto) = /^(.*?\S)\s+(\S+)\s*($ExtUtils::ParseXS::Constants::PrototypeRegexp*)$/o or warn("Warning: File '$filename' Line $lineno '$line' TYPEMAP entry needs 2 or 3 columns\n"), next; # prototype defaults to '$' $proto = '$' unless $proto; warn("Warning: File '$filename' Line $lineno '$line' Invalid prototype '$proto'\n") unless _valid_proto_string($proto); $self->add_typemap( ExtUtils::Typemaps::Type->new( xstype => $kind, proto => $proto, ctype => $type ), @add_params ); } elsif (/^\s/) { s/\s+$//; $$current .= $$current eq '' ? $_ : "\n".$_; } elsif ($_ eq '') { next; } elsif ($section eq 'input') { s/\s+$//; push @input_expr, {xstype => $_, code => ''}; $current = \$input_expr[-1]{code}; } else { # output section s/\s+$//; push @output_expr, {xstype => $_, code => ''}; $current = \$output_expr[-1]{code}; } } # end while lines foreach my $inexpr (@input_expr) { $self->add_inputmap( ExtUtils::Typemaps::InputMap->new(%$inexpr), @add_params ); } foreach my $outexpr (@output_expr) { $self->add_outputmap( ExtUtils::Typemaps::OutputMap->new(%$outexpr), @add_params ); } return 1; } # taken from ExtUtils::ParseXS sub _valid_proto_string { my $string = shift; if ($string =~ /^$ExtUtils::ParseXS::Constants::PrototypeRegexp+$/o) { return $string; } return 0 ; } # taken from ExtUtils::ParseXS (C_string) sub _escape_backslashes { my $string = shift; $string =~ s[\\][\\\\]g; $string; } 1; package ExtUtils::Command; use 5.00503; use strict; require Exporter; use vars qw(@ISA @EXPORT @EXPORT_OK $VERSION); @ISA = qw(Exporter); @EXPORT = qw(cp rm_f rm_rf mv cat eqtime mkpath touch test_f test_d chmod dos2unix); $VERSION = '7.34'; $VERSION = eval $VERSION; my $Is_VMS = $^O eq 'VMS'; my $Is_VMS_mode = $Is_VMS; my $Is_VMS_noefs = $Is_VMS; my $Is_Win32 = $^O eq 'MSWin32'; if( $Is_VMS ) { my $vms_unix_rpt; my $vms_efs; my $vms_case; if (eval { local $SIG{__DIE__}; local @INC = @INC; pop @INC if $INC[-1] eq '.'; require VMS::Feature; }) { $vms_unix_rpt = VMS::Feature::current("filename_unix_report"); $vms_efs = VMS::Feature::current("efs_charset"); $vms_case = VMS::Feature::current("efs_case_preserve"); } else { my $unix_rpt = $ENV{'DECC$FILENAME_UNIX_REPORT'} || ''; my $efs_charset = $ENV{'DECC$EFS_CHARSET'} || ''; my $efs_case = $ENV{'DECC$EFS_CASE_PRESERVE'} || ''; $vms_unix_rpt = $unix_rpt =~ /^[ET1]/i; $vms_efs = $efs_charset =~ /^[ET1]/i; $vms_case = $efs_case =~ /^[ET1]/i; } $Is_VMS_mode = 0 if $vms_unix_rpt; $Is_VMS_noefs = 0 if ($vms_efs); } # VMS uses % instead of ? to mean "one character" my $wild_regex = $Is_VMS ? '*%' : '*?'; sub expand_wildcards { @ARGV = map(/[$wild_regex]/o ? glob($_) : $_,@ARGV); } sub cat () { expand_wildcards(); print while (<>); } sub eqtime { my ($src,$dst) = @ARGV; local @ARGV = ($dst); touch(); # in case $dst doesn't exist utime((stat($src))[8,9],$dst); } sub rm_rf { expand_wildcards(); require File::Path; File::Path::rmtree([grep -e $_,@ARGV],0,0); } sub rm_f { expand_wildcards(); foreach my $file (@ARGV) { next unless -f $file; next if _unlink($file); chmod(0777, $file); next if _unlink($file); require Carp; Carp::carp("Cannot delete $file: $!"); } } sub _unlink { my $files_unlinked = 0; foreach my $file (@_) { my $delete_count = 0; $delete_count++ while unlink $file; $files_unlinked++ if $delete_count; } return $files_unlinked; } sub touch { my $t = time; expand_wildcards(); foreach my $file (@ARGV) { open(FILE,">>$file") || die "Cannot write $file:$!"; close(FILE); utime($t,$t,$file); } } sub mv { expand_wildcards(); my @src = @ARGV; my $dst = pop @src; if (@src > 1 && ! -d $dst) { require Carp; Carp::croak("Too many arguments"); } require File::Copy; my $nok = 0; foreach my $src (@src) { $nok ||= !File::Copy::move($src,$dst); } return !$nok; } sub cp { expand_wildcards(); my @src = @ARGV; my $dst = pop @src; if (@src > 1 && ! -d $dst) { require Carp; Carp::croak("Too many arguments"); } require File::Copy; my $nok = 0; foreach my $src (@src) { $nok ||= !File::Copy::copy($src,$dst); # Win32 does not update the mod time of a copied file, just the # created time which make does not look at. utime(time, time, $dst) if $Is_Win32; } return $nok; } sub chmod { local @ARGV = @ARGV; my $mode = shift(@ARGV); expand_wildcards(); if( $Is_VMS_mode && $Is_VMS_noefs) { require File::Spec; foreach my $idx (0..$#ARGV) { my $path = $ARGV[$idx]; next unless -d $path; # chmod 0777, [.foo.bar] doesn't work on VMS, you have to do # chmod 0777, [.foo]bar.dir my @dirs = File::Spec->splitdir( $path ); $dirs[-1] .= '.dir'; $path = File::Spec->catfile(@dirs); $ARGV[$idx] = $path; } } chmod(oct $mode,@ARGV) || die "Cannot chmod ".join(' ',$mode,@ARGV).":$!"; } sub mkpath { expand_wildcards(); require File::Path; File::Path::mkpath([@ARGV],0,0777); } sub test_f { exit(-f $ARGV[0] ? 0 : 1); } sub test_d { exit(-d $ARGV[0] ? 0 : 1); } sub dos2unix { require File::Find; File::Find::find(sub { return if -d; return unless -w _; return unless -r _; return if -B _; local $\; my $orig = $_; my $temp = '.dos2unix_tmp'; open ORIG, $_ or do { warn "dos2unix can't open $_: $!"; return }; open TEMP, ">$temp" or do { warn "dos2unix can't create .dos2unix_tmp: $!"; return }; binmode ORIG; binmode TEMP; while (my $line = ) { $line =~ s/\015\012/\012/g; print TEMP $line; } close ORIG; close TEMP; rename $temp, $orig; }, @ARGV); } package ExtUtils::MM_NW5; use strict; use ExtUtils::MakeMaker::Config; use File::Basename; our $VERSION = '7.34'; $VERSION = eval $VERSION; require ExtUtils::MM_Win32; our @ISA = qw(ExtUtils::MM_Win32); use ExtUtils::MakeMaker qw(&neatvalue &_sprintf562); $ENV{EMXSHELL} = 'sh'; # to run `commands` my $BORLAND = $Config{'cc'} =~ /\bbcc/i; my $GCC = $Config{'cc'} =~ /\bgcc/i; sub os_flavor { my $self = shift; return ($self->SUPER::os_flavor, 'Netware'); } sub init_platform { my($self) = shift; # To get Win32's setup. $self->SUPER::init_platform; # incpath is copied to makefile var INCLUDE in constants sub, here just # make it empty my $libpth = $Config{'libpth'}; $libpth =~ s( )(;); $self->{'LIBPTH'} = $libpth; $self->{'BASE_IMPORT'} = $Config{'base_import'}; # Additional import file specified from Makefile.pl if($self->{'base_import'}) { $self->{'BASE_IMPORT'} .= ', ' . $self->{'base_import'}; } $self->{'NLM_VERSION'} = $Config{'nlm_version'}; $self->{'MPKTOOL'} = $Config{'mpktool'}; $self->{'TOOLPATH'} = $Config{'toolpath'}; (my $boot = $self->{'NAME'}) =~ s/:/_/g; $self->{'BOOT_SYMBOL'}=$boot; # If the final binary name is greater than 8 chars, # truncate it here. if(length($self->{'BASEEXT'}) > 8) { $self->{'NLM_SHORT_NAME'} = substr($self->{'BASEEXT'},0,8); } # Get the include path and replace the spaces with ; # Copy this to makefile as INCLUDE = d:\...;d:\; ($self->{INCLUDE} = $Config{'incpath'}) =~ s/([ ]*)-I/;/g; # Set the path to CodeWarrior binaries which might not have been set in # any other place $self->{PATH} = '$(PATH);$(TOOLPATH)'; $self->{MM_NW5_VERSION} = $VERSION; } sub platform_constants { my($self) = shift; my $make_frag = ''; # Setup Win32's constants. $make_frag .= $self->SUPER::platform_constants; foreach my $macro (qw(LIBPTH BASE_IMPORT NLM_VERSION MPKTOOL TOOLPATH BOOT_SYMBOL NLM_SHORT_NAME INCLUDE PATH MM_NW5_VERSION )) { next unless defined $self->{$macro}; $make_frag .= "$macro = $self->{$macro}\n"; } return $make_frag; } sub static_lib_pure_cmd { my ($self, $src) = @_; $src =~ s/(\$\(\w+)(\))/$1:^"+"$2/g if $BORLAND; sprintf qq{\t\$(AR) %s\n}, ($BORLAND ? '$@ ' . $src : ($GCC ? '-ru $@ ' . $src : '-type library -o $@ ' . $src)); } sub xs_static_lib_is_xs { return 1; } sub xs_make_dynamic_lib { my ($self, $attribs, $from, $to, $todir, $ldfrom, $exportlist) = @_; my @m; # Taking care of long names like FileHandle, ByteLoader, SDBM_File etc if ($to =~ /^\$/) { if ($self->{NLM_SHORT_NAME}) { # deal with shortnames my $newto = q{$(INST_AUTODIR)\\$(NLM_SHORT_NAME).$(DLEXT)}; push @m, "$to: $newto\n\n"; $to = $newto; } } else { my ($v, $d, $f) = File::Spec->splitpath($to); # relies on $f having a literal "." in it, unlike for $(OBJ_EXT) if ($f =~ /[^\.]{9}\./) { # 9+ chars before '.', need to shorten $f = substr $f, 0, 8; } my $newto = File::Spec->catpath($v, $d, $f); push @m, "$to: $newto\n\n"; $to = $newto; } # bits below should be in dlsyms, not here # 1 2 3 4 push @m, _sprintf562 <<'MAKE_FRAG', $to, $from, $todir, $exportlist; # Create xdc data for an MT safe NLM in case of mpk build %1$s: %2$s $(MYEXTLIB) $(BOOTSTRAP) %3$s$(DFSEP).exists $(NOECHO) $(ECHO) Export boot_$(BOOT_SYMBOL) > %4$s $(NOECHO) $(ECHO) $(BASE_IMPORT) >> %4$s $(NOECHO) $(ECHO) Import @$(PERL_INC)\perl.imp >> %4$s MAKE_FRAG if ( $self->{CCFLAGS} =~ m/ -DMPK_ON /) { (my $xdc = $exportlist) =~ s#def\z#xdc#; $xdc = '$(BASEEXT).xdc'; push @m, sprintf <<'MAKE_FRAG', $xdc, $exportlist; $(MPKTOOL) $(XDCFLAGS) %s $(NOECHO) $(ECHO) xdcdata $(BASEEXT).xdc >> %s MAKE_FRAG } # Reconstruct the X.Y.Z version. my $version = join '.', map { sprintf "%d", $_ } $] =~ /(\d)\.(\d{3})(\d{2})/; push @m, sprintf <<'EOF', $from, $version, $to, $exportlist; $(LD) $(LDFLAGS) %s -desc "Perl %s Extension ($(BASEEXT)) XS_VERSION: $(XS_VERSION)" -nlmversion $(NLM_VERSION) -o %s $(MYEXTLIB) $(PERL_INC)\Main.lib -commandfile %s $(CHMOD) 755 $@ EOF join '', @m; } 1; __END__ package ExtUtils::MM_Any; use strict; our $VERSION = '7.34'; $VERSION = eval $VERSION; use Carp; use File::Spec; use File::Basename; BEGIN { our @ISA = qw(File::Spec); } # We need $Verbose use ExtUtils::MakeMaker qw($Verbose neatvalue _sprintf562); use ExtUtils::MakeMaker::Config; # So we don't have to keep calling the methods over and over again, # we have these globals to cache the values. Faster and shrtr. my $Curdir = __PACKAGE__->curdir; #my $Updir = __PACKAGE__->updir; my $METASPEC_URL = 'https://metacpan.org/pod/CPAN::Meta::Spec'; my $METASPEC_V = 2; sub os_flavor_is { my $self = shift; my %flavors = map { ($_ => 1) } $self->os_flavor; return (grep { $flavors{$_} } @_) ? 1 : 0; } sub can_load_xs { return defined &DynaLoader::boot_DynaLoader ? 1 : 0; } sub can_run { my ($self, $command) = @_; # a lot of VMS executables have a symbol defined # check those first if ( $^O eq 'VMS' ) { require VMS::DCLsym; my $syms = VMS::DCLsym->new; return $command if scalar $syms->getsym( uc $command ); } my @possibles; if( File::Spec->file_name_is_absolute($command) ) { return $self->maybe_command($command); } else { for my $dir ( File::Spec->path, File::Spec->curdir ) { next if ! $dir || ! -d $dir; my $abs = File::Spec->catfile($self->os_flavor_is('Win32') ? Win32::GetShortPathName( $dir ) : $dir, $command); push @possibles, $abs if $abs = $self->maybe_command($abs); } } return @possibles if wantarray; return shift @possibles; } sub can_redirect_error { my $self = shift; $self->os_flavor_is('Unix') or ($self->os_flavor_is('Win32') and !$self->os_flavor_is('Win9x')) or $self->os_flavor_is('OS/2') } my %maketype2true; # undocumented - so t/cd.t can still do its thing sub _clear_maketype_cache { %maketype2true = () } sub is_make_type { my($self, $type) = @_; return $maketype2true{$type} if defined $maketype2true{$type}; (undef, undef, my $make_basename) = $self->splitpath($self->make); return $maketype2true{$type} = 1 if $make_basename =~ /\b$type\b/i; # executable's filename return $maketype2true{$type} = 0 if $make_basename =~ /\b[gdn]make\b/i; # Never fall through for dmake/nmake/gmake # now have to run with "-v" and guess my $redirect = $self->can_redirect_error ? '2>&1' : ''; my $make = $self->make || $self->{MAKE}; my $minus_v = `"$make" -v $redirect`; return $maketype2true{$type} = 1 if $type eq 'gmake' and $minus_v =~ /GNU make/i; return $maketype2true{$type} = 1 if $type eq 'bsdmake' and $minus_v =~ /^usage: make \[-BeikNnqrstWwX\]/im; $maketype2true{$type} = 0; # it wasn't whatever you asked } my $cached_dep_space; sub can_dep_space { my $self = shift; return $cached_dep_space if defined $cached_dep_space; return $cached_dep_space = 1 if $self->is_make_type('gmake'); return $cached_dep_space = 0 if $self->is_make_type('dmake'); # only on W32 return $cached_dep_space = 0 if $self->is_make_type('bsdmake'); return $cached_dep_space = 0; # assume no } sub quote_dep { my ($self, $arg) = @_; die <can_dep_space; Tried to use make dependency with space for make that can't: '$arg' EOF $arg =~ s/( )/\\$1/g; # how GNU make does it return $arg; } sub split_command { my($self, $cmd, @args) = @_; my @cmds = (); return(@cmds) unless @args; # If the command was given as a here-doc, there's probably a trailing # newline. chomp $cmd; # set aside 30% for macro expansion. my $len_left = int($self->max_exec_len * 0.70); $len_left -= length $self->_expand_macros($cmd); do { my $arg_str = ''; my @next_args; while( @next_args = splice(@args, 0, 2) ) { # Two at a time to preserve pairs. my $next_arg_str = "\t ". join ' ', @next_args, "\n"; if( !length $arg_str ) { $arg_str .= $next_arg_str } elsif( length($arg_str) + length($next_arg_str) > $len_left ) { unshift @args, @next_args; last; } else { $arg_str .= $next_arg_str; } } chop $arg_str; push @cmds, $self->escape_newlines("$cmd \n$arg_str"); } while @args; return @cmds; } sub _expand_macros { my($self, $cmd) = @_; $cmd =~ s{\$\((\w+)\)}{ defined $self->{$1} ? $self->{$1} : "\$($1)" }e; return $cmd; } # override if this isn't suitable! sub make_type { return 'Unix-style'; } sub stashmeta { my($self, $text, $file) = @_; $self->echo($text, $file, { allow_variables => 0, append => 0 }); } sub echo { my($self, $text, $file, $opts) = @_; # Compatibility with old options if( !ref $opts ) { my $append = $opts; $opts = { append => $append || 0 }; } $opts->{allow_variables} = 0 unless defined $opts->{allow_variables}; my $ql_opts = { allow_variables => $opts->{allow_variables} }; my @cmds = map { '$(NOECHO) $(ECHO) '.$self->quote_literal($_, $ql_opts) } split /\n/, $text; if( $file ) { my $redirect = $opts->{append} ? '>>' : '>'; $cmds[0] .= " $redirect $file"; $_ .= " >> $file" foreach @cmds[1..$#cmds]; } return @cmds; } sub wraplist { my $self = shift; return join " \\\n\t", @_; } sub maketext_filter { return $_[1] } sub escape_dollarsigns { my($self, $text) = @_; # Escape dollar signs which are not starting a variable $text =~ s{\$ (?!\() }{\$\$}gx; return $text; } sub escape_all_dollarsigns { my($self, $text) = @_; # Escape dollar signs $text =~ s{\$}{\$\$}gx; return $text; } sub make { my $self = shift; my $make = lc $self->{MAKE}; # Truncate anything like foomake6 to just foomake. $make =~ s/^(\w+make).*/$1/; # Turn gnumake into gmake. $make =~ s/^gnu/g/; return $make; } sub all_target { my $self = shift; return <<'MAKE_EXT'; all :: pure_all $(NOECHO) $(NOOP) MAKE_EXT } sub _xs_list_basenames { my ($self) = @_; map { (my $b = $_) =~ s/\.xs$//; $b } sort keys %{ $self->{XS} }; } sub blibdirs_target { my $self = shift; my @dirs = map { uc "\$(INST_$_)" } qw(libdir archlib autodir archautodir bin script man1dir man3dir ); if ($self->{XSMULTI}) { for my $ext ($self->_xs_list_basenames) { my ($v, $d, $f) = File::Spec->splitpath($ext); my @d = File::Spec->splitdir($d); shift @d if $d[0] eq 'lib'; push @dirs, $self->catdir('$(INST_ARCHLIB)', 'auto', @d, $f); } } my @exists = map { $_.'$(DFSEP).exists' } @dirs; my $make = sprintf <<'MAKE', join(' ', @exists); blibdirs : %s $(NOECHO) $(NOOP) # Backwards compat with 6.18 through 6.25 blibdirs.ts : blibdirs $(NOECHO) $(NOOP) MAKE $make .= $self->dir_target(@dirs); return $make; } sub clean { # --- Cleanup and Distribution Sections --- my($self, %attribs) = @_; my @m; push(@m, ' # Delete temporary files but do not touch installed files. We don\'t delete # the Makefile here so a later make realclean still has a makefile to use. clean :: clean_subdirs '); my @files = sort values %{$self->{XS}}; # .c files from *.xs files push @files, map { my $file = $_; map { $file.$_ } $self->{OBJ_EXT}, qw(.def _def.old .bs .bso .exp .base); } $self->_xs_list_basenames; my @dirs = qw(blib); # Normally these are all under blib but they might have been # redefined. # XXX normally this would be a good idea, but the Perl core sets # INST_LIB = ../../lib rather than actually installing the files. # So a "make clean" in an ext/ directory would blow away lib. # Until the core is adjusted let's leave this out. # push @dirs, qw($(INST_ARCHLIB) $(INST_LIB) # $(INST_BIN) $(INST_SCRIPT) # $(INST_MAN1DIR) $(INST_MAN3DIR) # $(INST_LIBDIR) $(INST_ARCHLIBDIR) $(INST_AUTODIR) # $(INST_STATIC) $(INST_DYNAMIC) # ); if( $attribs{FILES} ) { # Use @dirs because we don't know what's in here. push @dirs, ref $attribs{FILES} ? @{$attribs{FILES}} : split /\s+/, $attribs{FILES} ; } push(@files, qw[$(MAKE_APERL_FILE) MYMETA.json MYMETA.yml perlmain.c tmon.out mon.out so_locations blibdirs.ts pm_to_blib pm_to_blib.ts *$(OBJ_EXT) *$(LIB_EXT) perl.exe perl perl$(EXE_EXT) $(BOOTSTRAP) $(BASEEXT).bso $(BASEEXT).def lib$(BASEEXT).def $(BASEEXT).exp $(BASEEXT).x ]); push(@files, $self->catfile('$(INST_ARCHAUTODIR)','extralibs.all')); push(@files, $self->catfile('$(INST_ARCHAUTODIR)','extralibs.ld')); # core files if ($^O eq 'vos') { push(@files, qw[perl*.kp]); } else { push(@files, qw[core core.*perl.*.? *perl.core]); } push(@files, map { "core." . "[0-9]"x$_ } (1..5)); # OS specific things to clean up. Use @dirs since we don't know # what might be in here. push @dirs, $self->extra_clean_files; # Occasionally files are repeated several times from different sources { my(%f) = map { ($_ => 1) } @files; @files = sort keys %f; } { my(%d) = map { ($_ => 1) } @dirs; @dirs = sort keys %d; } push @m, map "\t$_\n", $self->split_command('- $(RM_F)', @files); push @m, map "\t$_\n", $self->split_command('- $(RM_RF)', @dirs); # Leave Makefile.old around for realclean push @m, <<'MAKE'; $(NOECHO) $(RM_F) $(MAKEFILE_OLD) - $(MV) $(FIRST_MAKEFILE) $(MAKEFILE_OLD) $(DEV_NULL) MAKE push(@m, "\t$attribs{POSTOP}\n") if $attribs{POSTOP}; join("", @m); } sub clean_subdirs_target { my($self) = shift; # No subdirectories, no cleaning. return <<'NOOP_FRAG' unless @{$self->{DIR}}; clean_subdirs : $(NOECHO) $(NOOP) NOOP_FRAG my $clean = "clean_subdirs :\n"; for my $dir (@{$self->{DIR}}) { my $subclean = $self->oneliner(sprintf <<'CODE', $dir); exit 0 unless chdir '%s'; system '$(MAKE) clean' if -f '$(FIRST_MAKEFILE)'; CODE $clean .= "\t$subclean\n"; } return $clean; } sub dir_target { my($self, @dirs) = @_; my $make = ''; foreach my $dir (@dirs) { $make .= sprintf <<'MAKE', ($dir) x 4; %s$(DFSEP).exists :: Makefile.PL $(NOECHO) $(MKPATH) %s $(NOECHO) $(CHMOD) $(PERM_DIR) %s $(NOECHO) $(TOUCH) %s$(DFSEP).exists MAKE } return $make; } # For backwards compatibility. *dist_dir = *distdir; sub distdir { my($self) = shift; my $meta_target = $self->{NO_META} ? '' : 'distmeta'; my $sign_target = !$self->{SIGN} ? '' : 'distsignature'; return sprintf <<'MAKE_FRAG', $meta_target, $sign_target; create_distdir : $(RM_RF) $(DISTVNAME) $(PERLRUN) "-MExtUtils::Manifest=manicopy,maniread" \ -e "manicopy(maniread(),'$(DISTVNAME)', '$(DIST_CP)');" distdir : create_distdir %s %s $(NOECHO) $(NOOP) MAKE_FRAG } sub dist_test { my($self) = shift; my $mpl_args = join " ", map qq["$_"], @ARGV; my $test = $self->cd('$(DISTVNAME)', '$(ABSPERLRUN) Makefile.PL '.$mpl_args, '$(MAKE) $(PASTHRU)', '$(MAKE) test $(PASTHRU)' ); return sprintf <<'MAKE_FRAG', $test; disttest : distdir %s MAKE_FRAG } sub xs_dlsyms_arg { return ''; } sub xs_dlsyms_ext { die "Pure virtual method"; } sub xs_dlsyms_extra { ''; } sub xs_dlsyms_iterator { my ($self, $attribs) = @_; if ($self->{XSMULTI}) { my @m; for my $ext ($self->_xs_list_basenames) { my @parts = File::Spec->splitdir($ext); shift @parts if $parts[0] eq 'lib'; my $name = join '::', @parts; push @m, $self->xs_make_dlsyms( $attribs, $ext . $self->xs_dlsyms_ext, "$ext.xs", $name, $parts[-1], {}, [], {}, [], $self->xs_dlsyms_extra . q!, 'FILE' => ! . neatvalue($ext), ); } return join "\n", @m; } else { return $self->xs_make_dlsyms( $attribs, $self->{BASEEXT} . $self->xs_dlsyms_ext, 'Makefile.PL', $self->{NAME}, $self->{DLBASE}, $attribs->{DL_FUNCS} || $self->{DL_FUNCS} || {}, $attribs->{FUNCLIST} || $self->{FUNCLIST} || [], $attribs->{IMPORTS} || $self->{IMPORTS} || {}, $attribs->{DL_VARS} || $self->{DL_VARS} || [], $self->xs_dlsyms_extra, ); } } sub xs_make_dlsyms { my ($self, $attribs, $target, $dep, $name, $dlbase, $funcs, $funclist, $imports, $vars, $extra) = @_; my @m = ( "\n$target: $dep\n", q! $(PERLRUN) -MExtUtils::Mksymlists \\ -e "Mksymlists('NAME'=>\"!, $name, q!\", 'DLBASE' => '!,$dlbase, # The above two lines quoted differently to work around # a bug in the 4DOS/4NT command line interpreter. The visible # result of the bug was files named q('extension_name',) *with the # single quotes and the comma* in the extension build directories. q!', 'DL_FUNCS' => !,neatvalue($funcs), q!, 'FUNCLIST' => !,neatvalue($funclist), q!, 'IMPORTS' => !,neatvalue($imports), q!, 'DL_VARS' => !, neatvalue($vars) ); push @m, $extra if defined $extra; push @m, qq!);"\n!; join '', @m; } sub dynamic { # --- Dynamic Loading Sections --- my($self) = shift; ' dynamic :: $(FIRST_MAKEFILE) config $(INST_BOOT) $(INST_DYNAMIC) $(NOECHO) $(NOOP) '; } sub makemakerdflt_target { return <<'MAKE_FRAG'; makemakerdflt : all $(NOECHO) $(NOOP) MAKE_FRAG } sub manifypods_target { my($self) = shift; my $man1pods = ''; my $man3pods = ''; my $dependencies = ''; # populate manXpods & dependencies: foreach my $name (sort keys %{$self->{MAN1PODS}}, sort keys %{$self->{MAN3PODS}}) { $dependencies .= " \\\n\t$name"; } my $manify = <{"MAN${section}PODS"}; my $p2m = sprintf <<'CMD', $section, $] > 5.008 ? " -u" : ""; $(NOECHO) $(POD2MAN) --section=%s --perm_rw=$(PERM_RW)%s CMD push @man_cmds, $self->split_command($p2m, map {($_,$pods->{$_})} sort keys %$pods); } $manify .= "\t\$(NOECHO) \$(NOOP)\n" unless @man_cmds; $manify .= join '', map { "$_\n" } @man_cmds; return $manify; } { my $has_cpan_meta; sub _has_cpan_meta { return $has_cpan_meta if defined $has_cpan_meta; return $has_cpan_meta = !!eval { require CPAN::Meta; CPAN::Meta->VERSION(2.112150); 1; }; } } sub metafile_target { my $self = shift; return <<'MAKE_FRAG' if $self->{NO_META} or ! _has_cpan_meta(); metafile : $(NOECHO) $(NOOP) MAKE_FRAG my $metadata = $self->metafile_data( $self->{META_ADD} || {}, $self->{META_MERGE} || {}, ); my $meta = $self->_fix_metadata_before_conversion( $metadata ); my @write_metayml = $self->stashmeta( $meta->as_string({version => "1.4"}), 'META_new.yml' ); my @write_metajson = $self->stashmeta( $meta->as_string({version => "2.0"}), 'META_new.json' ); my $metayml = join("\n\t", @write_metayml); my $metajson = join("\n\t", @write_metajson); return sprintf <<'MAKE_FRAG', $metayml, $metajson; metafile : create_distdir $(NOECHO) $(ECHO) Generating META.yml %s -$(NOECHO) $(MV) META_new.yml $(DISTVNAME)/META.yml $(NOECHO) $(ECHO) Generating META.json %s -$(NOECHO) $(MV) META_new.json $(DISTVNAME)/META.json MAKE_FRAG } sub _fix_metadata_before_conversion { my ( $self, $metadata ) = @_; # we should never be called unless this already passed but # prefer to be defensive in case somebody else calls this return unless _has_cpan_meta; my $bad_version = $metadata->{version} && !CPAN::Meta::Validator->new->version( 'version', $metadata->{version} ); # just delete all invalid versions if( $bad_version ) { warn "Can't parse version '$metadata->{version}'\n"; $metadata->{version} = ''; } my $validator2 = CPAN::Meta::Validator->new( $metadata ); my @errors; push @errors, $validator2->errors if !$validator2->is_valid; my $validator14 = CPAN::Meta::Validator->new( { %$metadata, 'meta-spec' => { version => 1.4 }, } ); push @errors, $validator14->errors if !$validator14->is_valid; # fix non-camelcase custom resource keys (only other trick we know) for my $error ( @errors ) { my ( $key ) = ( $error =~ /Custom resource '(.*)' must be in CamelCase./ ); next if !$key; # first try to remove all non-alphabetic chars ( my $new_key = $key ) =~ s/[^_a-zA-Z]//g; # if that doesn't work, uppercase first one $new_key = ucfirst $new_key if !$validator14->custom_1( $new_key ); # copy to new key if that worked $metadata->{resources}{$new_key} = $metadata->{resources}{$key} if $validator14->custom_1( $new_key ); # and delete old one in any case delete $metadata->{resources}{$key}; } # paper over validation issues, but still complain, necessary because # there's no guarantee that the above will fix ALL errors my $meta = eval { CPAN::Meta->create( $metadata, { lazy_validation => 1 } ) }; warn $@ if $@ and $@ !~ /encountered CODE.*, but JSON can only represent references to arrays or hashes/; # use the original metadata straight if the conversion failed # or if it can't be stringified. if( !$meta || !eval { $meta->as_string( { version => $METASPEC_V } ) } || !eval { $meta->as_string } ) { $meta = bless $metadata, 'CPAN::Meta'; } my $now_license = $meta->as_struct({ version => 2 })->{license}; if ($self->{LICENSE} and $self->{LICENSE} ne 'unknown' and @{$now_license} == 1 and $now_license->[0] eq 'unknown' ) { warn "Invalid LICENSE value '$self->{LICENSE}' ignored\n"; } $meta; } sub _sort_pairs { my $sort = shift; my $pairs = shift; return map { $_ => $pairs->{$_} } sort $sort keys %$pairs; } # Taken from Module::Build::Base sub _hash_merge { my ($self, $h, $k, $v) = @_; if (ref $h->{$k} eq 'ARRAY') { push @{$h->{$k}}, ref $v ? @$v : $v; } elsif (ref $h->{$k} eq 'HASH') { $self->_hash_merge($h->{$k}, $_, $v->{$_}) foreach keys %$v; } else { $h->{$k} = $v; } } sub metafile_data { my $self = shift; my($meta_add, $meta_merge) = @_; $meta_add ||= {}; $meta_merge ||= {}; my $version = _normalize_version($self->{VERSION}); my $release_status = ($version =~ /_/) ? 'unstable' : 'stable'; my %meta = ( # required abstract => $self->{ABSTRACT} || 'unknown', author => defined($self->{AUTHOR}) ? $self->{AUTHOR} : ['unknown'], dynamic_config => 1, generated_by => "ExtUtils::MakeMaker version $ExtUtils::MakeMaker::VERSION", license => [ $self->{LICENSE} || 'unknown' ], 'meta-spec' => { url => $METASPEC_URL, version => $METASPEC_V, }, name => $self->{DISTNAME}, release_status => $release_status, version => $version, # optional no_index => { directory => [qw(t inc)] }, ); $self->_add_requirements_to_meta(\%meta); if (!eval { require JSON::PP; require CPAN::Meta::Converter; CPAN::Meta::Converter->VERSION(2.141170) }) { return \%meta; } # needs to be based on the original version my $v1_add = _metaspec_version($meta_add) !~ /^2/; my ($add_v, $merge_v) = map _metaspec_version($_), $meta_add, $meta_merge; for my $frag ($meta_add, $meta_merge) { my $def_v = $frag == $meta_add ? $merge_v : $add_v; $frag = CPAN::Meta::Converter->new($frag, default_version => $def_v)->upgrade_fragment; } # if we upgraded a 1.x _ADD fragment, we gave it a prereqs key that # will override all prereqs, which is more than the user asked for; # instead, we'll go inside the prereqs and override all those while( my($key, $val) = each %$meta_add ) { if ($v1_add and $key eq 'prereqs') { $meta{$key}{$_} = $val->{$_} for keys %$val; } elsif ($key ne 'meta-spec') { $meta{$key} = $val; } } while( my($key, $val) = each %$meta_merge ) { next if $key eq 'meta-spec'; $self->_hash_merge(\%meta, $key, $val); } return \%meta; } sub _add_requirements_to_meta { my ( $self, $meta ) = @_; # Check the original args so we can tell between the user setting it # to an empty hash and it just being initialized. $meta->{prereqs}{configure}{requires} = $self->{ARGS}{CONFIGURE_REQUIRES} ? $self->{CONFIGURE_REQUIRES} : { 'ExtUtils::MakeMaker' => 0, }; $meta->{prereqs}{build}{requires} = $self->{ARGS}{BUILD_REQUIRES} ? $self->{BUILD_REQUIRES} : { 'ExtUtils::MakeMaker' => 0, }; $meta->{prereqs}{test}{requires} = $self->{TEST_REQUIRES} if $self->{ARGS}{TEST_REQUIRES}; $meta->{prereqs}{runtime}{requires} = $self->{PREREQ_PM} if $self->{ARGS}{PREREQ_PM}; $meta->{prereqs}{runtime}{requires}{perl} = _normalize_version($self->{MIN_PERL_VERSION}) if $self->{MIN_PERL_VERSION}; } # spec version of given fragment - if not given, assume 1.4 sub _metaspec_version { my ( $meta ) = @_; return $meta->{'meta-spec'}->{version} if defined $meta->{'meta-spec'} and defined $meta->{'meta-spec'}->{version}; return '1.4'; } sub _add_requirements_to_meta_v1_4 { my ( $self, $meta ) = @_; # Check the original args so we can tell between the user setting it # to an empty hash and it just being initialized. if( $self->{ARGS}{CONFIGURE_REQUIRES} ) { $meta->{configure_requires} = $self->{CONFIGURE_REQUIRES}; } else { $meta->{configure_requires} = { 'ExtUtils::MakeMaker' => 0, }; } if( $self->{ARGS}{BUILD_REQUIRES} ) { $meta->{build_requires} = $self->{BUILD_REQUIRES}; } else { $meta->{build_requires} = { 'ExtUtils::MakeMaker' => 0, }; } if( $self->{ARGS}{TEST_REQUIRES} ) { $meta->{build_requires} = { %{ $meta->{build_requires} }, %{ $self->{TEST_REQUIRES} }, }; } $meta->{requires} = $self->{PREREQ_PM} if defined $self->{PREREQ_PM}; $meta->{requires}{perl} = _normalize_version($self->{MIN_PERL_VERSION}) if $self->{MIN_PERL_VERSION}; } # Adapted from Module::Build::Base sub _normalize_version { my ($version) = @_; $version = 0 unless defined $version; if ( ref $version eq 'version' ) { # version objects $version = $version->stringify; } elsif ( $version =~ /^[^v][^.]*\.[^.]+\./ ) { # no leading v, multiple dots # normalize string tuples without "v": "1.2.3" -> "v1.2.3" $version = "v$version"; } else { # leave alone } return $version; } sub _dump_hash { croak "first argument should be a hash ref" unless ref $_[0] eq 'HASH'; my $options = shift; my %hash = @_; # Use a list to preserve order. my @pairs; my $k_sort = exists $options->{key_sort} ? $options->{key_sort} : sub { lc $a cmp lc $b }; if ($k_sort) { croak "'key_sort' should be a coderef" unless ref $k_sort eq 'CODE'; @pairs = _sort_pairs($k_sort, \%hash); } else { # list of pairs, no sorting @pairs = @_; } my $yaml = $options->{use_header} ? "--- #YAML:1.0\n" : ''; my $indent = $options->{indent} || ''; my $k_length = min( ($options->{max_key_length} || 20), max(map { length($_) + 1 } grep { !ref $hash{$_} } keys %hash) ); my $customs = $options->{customs} || {}; # printf format for key my $k_format = "%-${k_length}s"; while( @pairs ) { my($key, $val) = splice @pairs, 0, 2; $val = '~' unless defined $val; if(ref $val eq 'HASH') { if ( keys %$val ) { my %k_options = ( # options for recursive call delta => $options->{delta}, use_header => 0, indent => $indent . $options->{delta}, ); if (exists $customs->{$key}) { my %k_custom = %{$customs->{$key}}; foreach my $k (qw(key_sort max_key_length customs)) { $k_options{$k} = $k_custom{$k} if exists $k_custom{$k}; } } $yaml .= $indent . "$key:\n" . _dump_hash(\%k_options, %$val); } else { $yaml .= $indent . "$key: {}\n"; } } elsif (ref $val eq 'ARRAY') { if( @$val ) { $yaml .= $indent . "$key:\n"; for (@$val) { croak "only nested arrays of non-refs are supported" if ref $_; $yaml .= $indent . $options->{delta} . "- $_\n"; } } else { $yaml .= $indent . "$key: []\n"; } } elsif( ref $val and !blessed($val) ) { croak "only nested hashes, arrays and objects are supported"; } else { # if it's an object, just stringify it $yaml .= $indent . sprintf "$k_format %s\n", "$key:", $val; } }; return $yaml; } sub blessed { return eval { $_[0]->isa("UNIVERSAL"); }; } sub max { return (sort { $b <=> $a } @_)[0]; } sub min { return (sort { $a <=> $b } @_)[0]; } sub metafile_file { my $self = shift; my %dump_options = ( use_header => 1, delta => ' ' x 4, key_sort => undef, ); return _dump_hash(\%dump_options, @_); } sub distmeta_target { my $self = shift; my @add_meta = ( $self->oneliner(<<'CODE', ['-MExtUtils::Manifest=maniadd']), exit unless -e q{META.yml}; eval { maniadd({q{META.yml} => q{Module YAML meta-data (added by MakeMaker)}}) } or die "Could not add META.yml to MANIFEST: ${'@'}" CODE $self->oneliner(<<'CODE', ['-MExtUtils::Manifest=maniadd']) exit unless -f q{META.json}; eval { maniadd({q{META.json} => q{Module JSON meta-data (added by MakeMaker)}}) } or die "Could not add META.json to MANIFEST: ${'@'}" CODE ); my @add_meta_to_distdir = map { $self->cd('$(DISTVNAME)', $_) } @add_meta; return sprintf <<'MAKE', @add_meta_to_distdir; distmeta : create_distdir metafile $(NOECHO) %s $(NOECHO) %s MAKE } sub mymeta { my $self = shift; my $file = shift || ''; # for testing my $mymeta = $self->_mymeta_from_meta($file); my $v2 = 1; unless ( $mymeta ) { $mymeta = $self->metafile_data( $self->{META_ADD} || {}, $self->{META_MERGE} || {}, ); $v2 = 0; } # Overwrite the non-configure dependency hashes $self->_add_requirements_to_meta($mymeta); $mymeta->{dynamic_config} = 0; return $mymeta; } sub _mymeta_from_meta { my $self = shift; my $metafile = shift || ''; # for testing return unless _has_cpan_meta(); my $meta; for my $file ( $metafile, "META.json", "META.yml" ) { next unless -e $file; eval { $meta = CPAN::Meta->load_file($file)->as_struct( { version => 2 } ); }; last if $meta; } return unless $meta; # META.yml before 6.25_01 cannot be trusted. META.yml lived in the source directory. # There was a good chance the author accidentally uploaded a stale META.yml if they # rolled their own tarball rather than using "make dist". if ($meta->{generated_by} && $meta->{generated_by} =~ /ExtUtils::MakeMaker version ([\d\._]+)/) { my $eummv = do { local $^W = 0; $1+0; }; if ($eummv < 6.2501) { return; } } return $meta; } sub write_mymeta { my $self = shift; my $mymeta = shift; return unless _has_cpan_meta(); my $meta_obj = $self->_fix_metadata_before_conversion( $mymeta ); $meta_obj->save( 'MYMETA.json', { version => "2.0" } ); $meta_obj->save( 'MYMETA.yml', { version => "1.4" } ); return 1; } sub realclean { my($self, %attribs) = @_; my @dirs = qw($(DISTVNAME)); my @files = qw($(FIRST_MAKEFILE) $(MAKEFILE_OLD)); # Special exception for the perl core where INST_* is not in blib. # This cleans up the files built from the ext/ directory (all XS). if( $self->{PERL_CORE} ) { push @dirs, qw($(INST_AUTODIR) $(INST_ARCHAUTODIR)); push @files, values %{$self->{PM}}; } if( $self->has_link_code ){ push @files, qw($(OBJECT)); } if( $attribs{FILES} ) { if( ref $attribs{FILES} ) { push @dirs, @{ $attribs{FILES} }; } else { push @dirs, split /\s+/, $attribs{FILES}; } } # Occasionally files are repeated several times from different sources { my(%f) = map { ($_ => 1) } @files; @files = sort keys %f; } { my(%d) = map { ($_ => 1) } @dirs; @dirs = sort keys %d; } my $rm_cmd = join "\n\t", map { "$_" } $self->split_command('- $(RM_F)', @files); my $rmf_cmd = join "\n\t", map { "$_" } $self->split_command('- $(RM_RF)', @dirs); my $m = sprintf <<'MAKE', $rm_cmd, $rmf_cmd; # Delete temporary files (via clean) and also delete dist files realclean purge :: realclean_subdirs %s %s MAKE $m .= "\t$attribs{POSTOP}\n" if $attribs{POSTOP}; return $m; } sub realclean_subdirs_target { my $self = shift; my @m = <<'EOF'; # so clean is forced to complete before realclean_subdirs runs realclean_subdirs : clean EOF return join '', @m, "\t\$(NOECHO) \$(NOOP)\n" unless @{$self->{DIR}}; foreach my $dir (@{$self->{DIR}}) { foreach my $makefile ('$(MAKEFILE_OLD)', '$(FIRST_MAKEFILE)' ) { my $subrclean .= $self->oneliner(_sprintf562 <<'CODE', $dir, $makefile); chdir '%1$s'; system '$(MAKE) $(USEMAKEFILE) %2$s realclean' if -f '%2$s'; CODE push @m, "\t- $subrclean\n"; } } return join '', @m; } sub signature_target { my $self = shift; return <<'MAKE_FRAG'; signature : cpansign -s MAKE_FRAG } sub distsignature_target { my $self = shift; my $add_sign = $self->oneliner(<<'CODE', ['-MExtUtils::Manifest=maniadd']); eval { maniadd({q{SIGNATURE} => q{Public-key signature (added by MakeMaker)}}) } or die "Could not add SIGNATURE to MANIFEST: ${'@'}" CODE my $sign_dist = $self->cd('$(DISTVNAME)' => 'cpansign -s'); # cpansign -s complains if SIGNATURE is in the MANIFEST yet does not # exist my $touch_sig = $self->cd('$(DISTVNAME)' => '$(TOUCH) SIGNATURE'); my $add_sign_to_dist = $self->cd('$(DISTVNAME)' => $add_sign ); return sprintf <<'MAKE', $add_sign_to_dist, $touch_sig, $sign_dist distsignature : distmeta $(NOECHO) %s $(NOECHO) %s %s MAKE } sub special_targets { my $make_frag = <<'MAKE_FRAG'; .SUFFIXES : .xs .c .C .cpp .i .s .cxx .cc $(OBJ_EXT) .PHONY: all config static dynamic test linkext manifest blibdirs clean realclean disttest distdir pure_all subdirs clean_subdirs makemakerdflt manifypods realclean_subdirs subdirs_dynamic subdirs_pure_nolink subdirs_static subdirs-test_dynamic subdirs-test_static test_dynamic test_static MAKE_FRAG $make_frag .= <<'MAKE_FRAG' if $ENV{CLEARCASE_ROOT}; .NO_CONFIG_REC: Makefile MAKE_FRAG return $make_frag; } sub init_ABSTRACT { my $self = shift; if( $self->{ABSTRACT_FROM} and $self->{ABSTRACT} ) { warn "Both ABSTRACT_FROM and ABSTRACT are set. ". "Ignoring ABSTRACT_FROM.\n"; return; } if ($self->{ABSTRACT_FROM}){ $self->{ABSTRACT} = $self->parse_abstract($self->{ABSTRACT_FROM}) or carp "WARNING: Setting ABSTRACT via file ". "'$self->{ABSTRACT_FROM}' failed\n"; } if ($self->{ABSTRACT} && $self->{ABSTRACT} =~ m![[:cntrl:]]+!) { warn "WARNING: ABSTRACT contains control character(s),". " they will be removed\n"; $self->{ABSTRACT} =~ s![[:cntrl:]]+!!g; return; } } sub init_INST { my($self) = shift; $self->{INST_ARCHLIB} ||= $self->catdir($Curdir,"blib","arch"); $self->{INST_BIN} ||= $self->catdir($Curdir,'blib','bin'); # INST_LIB typically pre-set if building an extension after # perl has been built and installed. Setting INST_LIB allows # you to build directly into, say $Config{privlibexp}. unless ($self->{INST_LIB}){ if ($self->{PERL_CORE}) { $self->{INST_LIB} = $self->{INST_ARCHLIB} = $self->{PERL_LIB}; } else { $self->{INST_LIB} = $self->catdir($Curdir,"blib","lib"); } } my @parentdir = split(/::/, $self->{PARENT_NAME}); $self->{INST_LIBDIR} = $self->catdir('$(INST_LIB)', @parentdir); $self->{INST_ARCHLIBDIR} = $self->catdir('$(INST_ARCHLIB)', @parentdir); $self->{INST_AUTODIR} = $self->catdir('$(INST_LIB)', 'auto', '$(FULLEXT)'); $self->{INST_ARCHAUTODIR} = $self->catdir('$(INST_ARCHLIB)', 'auto', '$(FULLEXT)'); $self->{INST_SCRIPT} ||= $self->catdir($Curdir,'blib','script'); $self->{INST_MAN1DIR} ||= $self->catdir($Curdir,'blib','man1'); $self->{INST_MAN3DIR} ||= $self->catdir($Curdir,'blib','man3'); return 1; } sub init_INSTALL { my($self) = shift; if( $self->{ARGS}{INSTALL_BASE} and $self->{ARGS}{PREFIX} ) { die "Only one of PREFIX or INSTALL_BASE can be given. Not both.\n"; } if( $self->{ARGS}{INSTALL_BASE} ) { $self->init_INSTALL_from_INSTALL_BASE; } else { $self->init_INSTALL_from_PREFIX; } } sub init_INSTALL_from_PREFIX { my $self = shift; $self->init_lib2arch; # There are often no Config.pm defaults for these new man variables so # we fall back to the old behavior which is to use installman*dir foreach my $num (1, 3) { my $k = 'installsiteman'.$num.'dir'; $self->{uc $k} ||= uc "\$(installman${num}dir)" unless $Config{$k}; } foreach my $num (1, 3) { my $k = 'installvendorman'.$num.'dir'; unless( $Config{$k} ) { $self->{uc $k} ||= $Config{usevendorprefix} ? uc "\$(installman${num}dir)" : ''; } } $self->{INSTALLSITEBIN} ||= '$(INSTALLBIN)' unless $Config{installsitebin}; $self->{INSTALLSITESCRIPT} ||= '$(INSTALLSCRIPT)' unless $Config{installsitescript}; unless( $Config{installvendorbin} ) { $self->{INSTALLVENDORBIN} ||= $Config{usevendorprefix} ? $Config{installbin} : ''; } unless( $Config{installvendorscript} ) { $self->{INSTALLVENDORSCRIPT} ||= $Config{usevendorprefix} ? $Config{installscript} : ''; } my $iprefix = $Config{installprefixexp} || $Config{installprefix} || $Config{prefixexp} || $Config{prefix} || ''; my $vprefix = $Config{usevendorprefix} ? $Config{vendorprefixexp} : ''; my $sprefix = $Config{siteprefixexp} || ''; # 5.005_03 doesn't have a siteprefix. $sprefix = $iprefix unless $sprefix; $self->{PREFIX} ||= ''; if( $self->{PREFIX} ) { @{$self}{qw(PERLPREFIX SITEPREFIX VENDORPREFIX)} = ('$(PREFIX)') x 3; } else { $self->{PERLPREFIX} ||= $iprefix; $self->{SITEPREFIX} ||= $sprefix; $self->{VENDORPREFIX} ||= $vprefix; # Lots of MM extension authors like to use $(PREFIX) so we # put something sensible in there no matter what. $self->{PREFIX} = '$('.uc $self->{INSTALLDIRS}.'PREFIX)'; } my $arch = $Config{archname}; my $version = $Config{version}; # default style my $libstyle = $Config{installstyle} || 'lib/perl5'; my $manstyle = ''; if( $self->{LIBSTYLE} ) { $libstyle = $self->{LIBSTYLE}; $manstyle = $self->{LIBSTYLE} eq 'lib/perl5' ? 'lib/perl5' : ''; } # Some systems, like VOS, set installman*dir to '' if they can't # read man pages. for my $num (1, 3) { $self->{'INSTALLMAN'.$num.'DIR'} ||= 'none' unless $Config{'installman'.$num.'dir'}; } my %bin_layouts = ( bin => { s => $iprefix, t => 'perl', d => 'bin' }, vendorbin => { s => $vprefix, t => 'vendor', d => 'bin' }, sitebin => { s => $sprefix, t => 'site', d => 'bin' }, script => { s => $iprefix, t => 'perl', d => 'bin' }, vendorscript=> { s => $vprefix, t => 'vendor', d => 'bin' }, sitescript => { s => $sprefix, t => 'site', d => 'bin' }, ); my %man_layouts = ( man1dir => { s => $iprefix, t => 'perl', d => 'man/man1', style => $manstyle, }, siteman1dir => { s => $sprefix, t => 'site', d => 'man/man1', style => $manstyle, }, vendorman1dir => { s => $vprefix, t => 'vendor', d => 'man/man1', style => $manstyle, }, man3dir => { s => $iprefix, t => 'perl', d => 'man/man3', style => $manstyle, }, siteman3dir => { s => $sprefix, t => 'site', d => 'man/man3', style => $manstyle, }, vendorman3dir => { s => $vprefix, t => 'vendor', d => 'man/man3', style => $manstyle, }, ); my %lib_layouts = ( privlib => { s => $iprefix, t => 'perl', d => '', style => $libstyle, }, vendorlib => { s => $vprefix, t => 'vendor', d => '', style => $libstyle, }, sitelib => { s => $sprefix, t => 'site', d => 'site_perl', style => $libstyle, }, archlib => { s => $iprefix, t => 'perl', d => "$version/$arch", style => $libstyle }, vendorarch => { s => $vprefix, t => 'vendor', d => "$version/$arch", style => $libstyle }, sitearch => { s => $sprefix, t => 'site', d => "site_perl/$version/$arch", style => $libstyle }, ); # Special case for LIB. if( $self->{LIB} ) { foreach my $var (keys %lib_layouts) { my $Installvar = uc "install$var"; if( $var =~ /arch/ ) { $self->{$Installvar} ||= $self->catdir($self->{LIB}, $Config{archname}); } else { $self->{$Installvar} ||= $self->{LIB}; } } } my %type2prefix = ( perl => 'PERLPREFIX', site => 'SITEPREFIX', vendor => 'VENDORPREFIX' ); my %layouts = (%bin_layouts, %man_layouts, %lib_layouts); while( my($var, $layout) = each(%layouts) ) { my($s, $t, $d, $style) = @{$layout}{qw(s t d style)}; my $r = '$('.$type2prefix{$t}.')'; warn "Prefixing $var\n" if $Verbose >= 2; my $installvar = "install$var"; my $Installvar = uc $installvar; next if $self->{$Installvar}; $d = "$style/$d" if $style; $self->prefixify($installvar, $s, $r, $d); warn " $Installvar == $self->{$Installvar}\n" if $Verbose >= 2; } # Generate these if they weren't figured out. $self->{VENDORARCHEXP} ||= $self->{INSTALLVENDORARCH}; $self->{VENDORLIBEXP} ||= $self->{INSTALLVENDORLIB}; return 1; } my %map = ( lib => [qw(lib perl5)], arch => [('lib', 'perl5', $Config{archname})], bin => [qw(bin)], man1dir => [qw(man man1)], man3dir => [qw(man man3)] ); $map{script} = $map{bin}; sub init_INSTALL_from_INSTALL_BASE { my $self = shift; @{$self}{qw(PREFIX VENDORPREFIX SITEPREFIX PERLPREFIX)} = '$(INSTALL_BASE)'; my %install; foreach my $thing (keys %map) { foreach my $dir (('', 'SITE', 'VENDOR')) { my $uc_thing = uc $thing; my $key = "INSTALL".$dir.$uc_thing; $install{$key} ||= $self->catdir('$(INSTALL_BASE)', @{$map{$thing}}); } } # Adjust for variable quirks. $install{INSTALLARCHLIB} ||= delete $install{INSTALLARCH}; $install{INSTALLPRIVLIB} ||= delete $install{INSTALLLIB}; foreach my $key (keys %install) { $self->{$key} ||= $install{$key}; } return 1; } sub init_VERSION { my($self) = shift; $self->{MAKEMAKER} = $ExtUtils::MakeMaker::Filename; $self->{MM_VERSION} = $ExtUtils::MakeMaker::VERSION; $self->{MM_REVISION}= $ExtUtils::MakeMaker::Revision; $self->{VERSION_FROM} ||= ''; if ($self->{VERSION_FROM}){ $self->{VERSION} = $self->parse_version($self->{VERSION_FROM}); if( $self->{VERSION} eq 'undef' ) { carp("WARNING: Setting VERSION via file ". "'$self->{VERSION_FROM}' failed\n"); } } if (defined $self->{VERSION}) { if ( $self->{VERSION} !~ /^\s*v?[\d_\.]+\s*$/ ) { require version; my $normal = eval { version->new( $self->{VERSION} ) }; $self->{VERSION} = $normal if defined $normal; } $self->{VERSION} =~ s/^\s+//; $self->{VERSION} =~ s/\s+$//; } else { $self->{VERSION} = ''; } $self->{VERSION_MACRO} = 'VERSION'; ($self->{VERSION_SYM} = $self->{VERSION}) =~ s/\W/_/g; $self->{DEFINE_VERSION} = '-D$(VERSION_MACRO)=\"$(VERSION)\"'; # Graham Barr and Paul Marquess had some ideas how to ensure # version compatibility between the *.pm file and the # corresponding *.xs file. The bottom line was, that we need an # XS_VERSION macro that defaults to VERSION: $self->{XS_VERSION} ||= $self->{VERSION}; $self->{XS_VERSION_MACRO} = 'XS_VERSION'; $self->{XS_DEFINE_VERSION} = '-D$(XS_VERSION_MACRO)=\"$(XS_VERSION)\"'; } sub init_tools { my $self = shift; $self->{ECHO} ||= $self->oneliner('binmode STDOUT, qq{:raw}; print qq{@ARGV}', ['-l']); $self->{ECHO_N} ||= $self->oneliner('print qq{@ARGV}'); $self->{TOUCH} ||= $self->oneliner('touch', ["-MExtUtils::Command"]); $self->{CHMOD} ||= $self->oneliner('chmod', ["-MExtUtils::Command"]); $self->{RM_F} ||= $self->oneliner('rm_f', ["-MExtUtils::Command"]); $self->{RM_RF} ||= $self->oneliner('rm_rf', ["-MExtUtils::Command"]); $self->{TEST_F} ||= $self->oneliner('test_f', ["-MExtUtils::Command"]); $self->{TEST_S} ||= $self->oneliner('test_s', ["-MExtUtils::Command::MM"]); $self->{CP_NONEMPTY} ||= $self->oneliner('cp_nonempty', ["-MExtUtils::Command::MM"]); $self->{FALSE} ||= $self->oneliner('exit 1'); $self->{TRUE} ||= $self->oneliner('exit 0'); $self->{MKPATH} ||= $self->oneliner('mkpath', ["-MExtUtils::Command"]); $self->{CP} ||= $self->oneliner('cp', ["-MExtUtils::Command"]); $self->{MV} ||= $self->oneliner('mv', ["-MExtUtils::Command"]); $self->{MOD_INSTALL} ||= $self->oneliner(<<'CODE', ['-MExtUtils::Install']); install([ from_to => {@ARGV}, verbose => '$(VERBINST)', uninstall_shadows => '$(UNINST)', dir_mode => '$(PERM_DIR)' ]); CODE $self->{DOC_INSTALL} ||= $self->oneliner('perllocal_install', ["-MExtUtils::Command::MM"]); $self->{UNINSTALL} ||= $self->oneliner('uninstall', ["-MExtUtils::Command::MM"]); $self->{WARN_IF_OLD_PACKLIST} ||= $self->oneliner('warn_if_old_packlist', ["-MExtUtils::Command::MM"]); $self->{FIXIN} ||= $self->oneliner('MY->fixin(shift)', ["-MExtUtils::MY"]); $self->{EQUALIZE_TIMESTAMP} ||= $self->oneliner('eqtime', ["-MExtUtils::Command"]); $self->{UNINST} ||= 0; $self->{VERBINST} ||= 0; $self->{SHELL} ||= $Config{sh}; # UMASK_NULL is not used by MakeMaker but some CPAN modules # make use of it. $self->{UMASK_NULL} ||= "umask 0"; # Not the greatest default, but its something. $self->{DEV_NULL} ||= "> /dev/null 2>&1"; $self->{NOOP} ||= '$(TRUE)'; $self->{NOECHO} = '@' unless defined $self->{NOECHO}; $self->{FIRST_MAKEFILE} ||= $self->{MAKEFILE} || 'Makefile'; $self->{MAKEFILE} ||= $self->{FIRST_MAKEFILE}; $self->{MAKEFILE_OLD} ||= $self->{MAKEFILE}.'.old'; $self->{MAKE_APERL_FILE} ||= $self->{MAKEFILE}.'.aperl'; # Not everybody uses -f to indicate "use this Makefile instead" $self->{USEMAKEFILE} ||= '-f'; # Some makes require a wrapper around macros passed in on the command # line. $self->{MACROSTART} ||= ''; $self->{MACROEND} ||= ''; return; } sub init_others { my $self = shift; $self->{LD_RUN_PATH} = ""; $self->{LIBS} = $self->_fix_libs($self->{LIBS}); # Compute EXTRALIBS, BSLOADLIBS and LDLOADLIBS from $self->{LIBS} foreach my $libs ( @{$self->{LIBS}} ){ $libs =~ s/^\s*(.*\S)\s*$/$1/; # remove leading and trailing whitespace my(@libs) = $self->extliblist($libs); if ($libs[0] or $libs[1] or $libs[2]){ # LD_RUN_PATH now computed by ExtUtils::Liblist ($self->{EXTRALIBS}, $self->{BSLOADLIBS}, $self->{LDLOADLIBS}, $self->{LD_RUN_PATH}) = @libs; last; } } if ( $self->{OBJECT} ) { $self->{OBJECT} = join(" ", @{$self->{OBJECT}}) if ref $self->{OBJECT}; $self->{OBJECT} =~ s!\.o(bj)?\b!\$(OBJ_EXT)!g; } elsif ( ($self->{MAGICXS} || $self->{XSMULTI}) && @{$self->{O_FILES}||[]} ) { $self->{OBJECT} = join(" ", @{$self->{O_FILES}}); $self->{OBJECT} =~ s!\.o(bj)?\b!\$(OBJ_EXT)!g; } else { # init_dirscan should have found out, if we have C files $self->{OBJECT} = ""; $self->{OBJECT} = '$(BASEEXT)$(OBJ_EXT)' if @{$self->{C}||[]}; } $self->{OBJECT} =~ s/\n+/ \\\n\t/g; $self->{BOOTDEP} = (-f "$self->{BASEEXT}_BS") ? "$self->{BASEEXT}_BS" : ""; $self->{PERLMAINCC} ||= '$(CC)'; $self->{LDFROM} = '$(OBJECT)' unless $self->{LDFROM}; # Sanity check: don't define LINKTYPE = dynamic if we're skipping # the 'dynamic' section of MM. We don't have this problem with # 'static', since we either must use it (%Config says we can't # use dynamic loading) or the caller asked for it explicitly. if (!$self->{LINKTYPE}) { $self->{LINKTYPE} = $self->{SKIPHASH}{'dynamic'} ? 'static' : ($Config{usedl} ? 'dynamic' : 'static'); } return; } # Lets look at $self->{LIBS} carefully: It may be an anon array, a string or # undefined. In any case we turn it into an anon array sub _fix_libs { my($self, $libs) = @_; return !defined $libs ? [''] : !ref $libs ? [$libs] : !defined $libs->[0] ? [''] : $libs ; } sub tools_other { my($self) = shift; my @m; # We set PM_FILTER as late as possible so it can see all the earlier # on macro-order sensitive makes such as nmake. for my $tool (qw{ SHELL CHMOD CP MV NOOP NOECHO RM_F RM_RF TEST_F TOUCH UMASK_NULL DEV_NULL MKPATH EQUALIZE_TIMESTAMP FALSE TRUE ECHO ECHO_N UNINST VERBINST MOD_INSTALL DOC_INSTALL UNINSTALL WARN_IF_OLD_PACKLIST MACROSTART MACROEND USEMAKEFILE PM_FILTER FIXIN CP_NONEMPTY } ) { next unless defined $self->{$tool}; push @m, "$tool = $self->{$tool}\n"; } return join "", @m; } sub init_platform { return ''; } sub init_MAKE { my $self = shift; $self->{MAKE} ||= $ENV{MAKE} || $Config{make}; } sub manifypods { my $self = shift; my $POD2MAN_macro = $self->POD2MAN_macro(); my $manifypods_target = $self->manifypods_target(); return <oneliner(sprintf <<'PERL_CODE', $maxlen); use AutoSplit; %s autosplit($$ARGV[0], $$ARGV[1], 0, 1, 1) PERL_CODE return sprintf <<'MAKE_FRAG', $asplit; # Usage: $(AUTOSPLITFILE) FileToSplit AutoDirToSplitInto AUTOSPLITFILE = %s MAKE_FRAG } sub arch_check { my $self = shift; my($pconfig, $cconfig) = @_; return 1 if $self->{PERL_SRC}; my($pvol, $pthinks) = $self->splitpath($pconfig); my($cvol, $cthinks) = $self->splitpath($cconfig); $pthinks = $self->canonpath($pthinks); $cthinks = $self->canonpath($cthinks); my $ret = 1; if ($pthinks ne $cthinks) { print "Have $pthinks\n"; print "Want $cthinks\n"; $ret = 0; my $arch = (grep length, $self->splitdir($pthinks))[-1]; print <{UNINSTALLED_PERL}; Your perl and your Config.pm seem to have different ideas about the architecture they are running on. Perl thinks: [$arch] Config says: [$Config{archname}] This may or may not cause problems. Please check your installation of perl if you have problems building this extension. END } return $ret; } sub catfile { my $self = shift; return $self->canonpath($self->SUPER::catfile(@_)); } sub find_tests { my($self) = shift; return -d 't' ? 't/*.t' : ''; } sub find_tests_recursive { my $self = shift; return $self->find_tests_recursive_in('t'); } sub find_tests_recursive_in { my($self, $dir) = @_; return '' unless -d $dir; require File::Find; my $base_depth = grep { $_ ne '' } File::Spec->splitdir( (File::Spec->splitpath($dir))[1] ); my %depths; my $wanted = sub { return unless m!\.t$!; my ($volume,$directories,$file) = File::Spec->splitpath( $File::Find::name ); my $depth = grep { $_ ne '' } File::Spec->splitdir( $directories ); $depth -= $base_depth; $depths{ $depth } = 1; }; File::Find::find( $wanted, $dir ); return join ' ', map { $dir . '/*' x $_ . '.t' } sort { $a <=> $b } keys %depths; } # An empty method here tickled a perl 5.8.1 bug and would return its object. sub extra_clean_files { return; } sub installvars { return qw(PRIVLIB SITELIB VENDORLIB ARCHLIB SITEARCH VENDORARCH BIN SITEBIN VENDORBIN SCRIPT SITESCRIPT VENDORSCRIPT MAN1DIR SITEMAN1DIR VENDORMAN1DIR MAN3DIR SITEMAN3DIR VENDORMAN3DIR ); } sub libscan { my($self,$path) = @_; if ($path =~ m<^README\.pod$>i) { warn "WARNING: Older versions of ExtUtils::MakeMaker may errantly install $path as part of this distribution. It is recommended to avoid using this path in CPAN modules.\n" unless $ENV{PERL_CORE}; return ''; } my($dirs,$file) = ($self->splitpath($path))[1,2]; return '' if grep /^(?:RCS|CVS|SCCS|\.svn|_darcs)$/, $self->splitdir($dirs), $file; return $path; } sub platform_constants { return ''; } sub post_constants { ""; } sub post_initialize { ""; } sub postamble { ""; } sub _PREREQ_PRINT { my $self = shift; require Data::Dumper; my @what = ('PREREQ_PM'); push @what, 'MIN_PERL_VERSION' if $self->{MIN_PERL_VERSION}; push @what, 'BUILD_REQUIRES' if $self->{BUILD_REQUIRES}; print Data::Dumper->Dump([@{$self}{@what}], \@what); exit 0; } sub _PRINT_PREREQ { my $self = shift; my $prereqs= $self->{PREREQ_PM}; my @prereq = map { [$_, $prereqs->{$_}] } keys %$prereqs; if ( $self->{MIN_PERL_VERSION} ) { push @prereq, ['perl' => $self->{MIN_PERL_VERSION}]; } print join(" ", map { "perl($_->[0])>=$_->[1] " } sort { $a->[0] cmp $b->[0] } @prereq), "\n"; exit 0; } sub _perl_header_files { my $self = shift; my $header_dir = $self->{PERL_SRC} || $ENV{PERL_SRC} || $self->catdir($Config{archlibexp}, 'CORE'); opendir my $dh, $header_dir or die "Failed to opendir '$header_dir' to find header files: $!"; # we need to use a temporary here as the sort in scalar context would have undefined results. my @perl_headers= sort grep { /\.h\z/ } readdir($dh); closedir $dh; return @perl_headers; } sub _perl_header_files_fragment { my ($self, $separator)= @_; $separator ||= ""; return join("\\\n", "PERL_HDRS = ", map { sprintf( " \$(PERL_INCDEP)%s%s ", $separator, $_ ) } $self->_perl_header_files() ) . "\n\n" . "\$(OBJECT) : \$(PERL_HDRS)\n"; } 1; package ExtUtils::MM_QNX; use strict; our $VERSION = '7.34'; $VERSION = eval $VERSION; require ExtUtils::MM_Unix; our @ISA = qw(ExtUtils::MM_Unix); sub extra_clean_files { my $self = shift; my @errfiles = @{$self->{C}}; for ( @errfiles ) { s/.c$/.err/; } return( @errfiles, 'perlmain.err' ); } 1; package ExtUtils::MM_VOS; use strict; our $VERSION = '7.34'; $VERSION = eval $VERSION; require ExtUtils::MM_Unix; our @ISA = qw(ExtUtils::MM_Unix); sub extra_clean_files { return qw(*.kp); } 1; package ExtUtils::MM_Darwin; use strict; BEGIN { require ExtUtils::MM_Unix; our @ISA = qw( ExtUtils::MM_Unix ); } our $VERSION = '7.34'; $VERSION = eval $VERSION; sub init_dist { my $self = shift; # Thank you, Apple, for breaking tar and then breaking the work around. # 10.4 wants COPY_EXTENDED_ATTRIBUTES_DISABLE while 10.5 wants # COPYFILE_DISABLE. I'm not going to push my luck and instead just # set both. $self->{TAR} ||= 'COPY_EXTENDED_ATTRIBUTES_DISABLE=1 COPYFILE_DISABLE=1 tar'; $self->SUPER::init_dist(@_); } 1; # $Id$ package ExtUtils::MakeMaker; use strict; BEGIN {require 5.006;} require Exporter; use ExtUtils::MakeMaker::Config; use ExtUtils::MakeMaker::version; # ensure we always have our fake version.pm use Carp; use File::Path; my $CAN_DECODE = eval { require ExtUtils::MakeMaker::Locale; }; # 2 birds, 1 stone eval { ExtUtils::MakeMaker::Locale::reinit('UTF-8') } if $CAN_DECODE and Encode::find_encoding('locale')->name eq 'ascii'; our $Verbose = 0; # exported our @Parent; # needs to be localized our @Get_from_Config; # referenced by MM_Unix our @MM_Sections; our @Overridable; my @Prepend_parent; my %Recognized_Att_Keys; our %macro_fsentity; # whether a macro is a filesystem name our %macro_dep; # whether a macro is a dependency our $VERSION = '7.34'; $VERSION = eval $VERSION; ## no critic [BuiltinFunctions::ProhibitStringyEval] # Emulate something resembling CVS $Revision$ (our $Revision = $VERSION) =~ s{_}{}; $Revision = int $Revision * 10000; our $Filename = __FILE__; # referenced outside MakeMaker our @ISA = qw(Exporter); our @EXPORT = qw(&WriteMakefile $Verbose &prompt &os_unsupported); our @EXPORT_OK = qw($VERSION &neatvalue &mkbootstrap &mksymlists &WriteEmptyMakefile &open_for_writing &write_file_via_tmp &_sprintf562); # These will go away once the last of the Win32 & VMS specific code is # purged. my $Is_VMS = $^O eq 'VMS'; my $Is_Win32 = $^O eq 'MSWin32'; our $UNDER_CORE = $ENV{PERL_CORE}; # needs to be our full_setup(); require ExtUtils::MM; # Things like CPAN assume loading ExtUtils::MakeMaker # will give them MM. require ExtUtils::MY; # XXX pre-5.8 versions of ExtUtils::Embed expect # loading ExtUtils::MakeMaker will give them MY. # This will go when Embed is its own CPAN module. # 5.6.2 can't do sprintf "%1$s" - this can only do %s sub _sprintf562 { my ($format, @args) = @_; for (my $i = 1; $i <= @args; $i++) { $format =~ s#%$i\$s#$args[$i-1]#g; } $format; } sub WriteMakefile { croak "WriteMakefile: Need even number of args" if @_ % 2; require ExtUtils::MY; my %att = @_; _convert_compat_attrs(\%att); _verify_att(\%att); my $mm = MM->new(\%att); $mm->flush; return $mm; } # Basic signatures of the attributes WriteMakefile takes. Each is the # reference type. Empty value indicate it takes a non-reference # scalar. my %Att_Sigs; my %Special_Sigs = ( AUTHOR => 'ARRAY', C => 'ARRAY', CONFIG => 'ARRAY', CONFIGURE => 'CODE', DIR => 'ARRAY', DL_FUNCS => 'HASH', DL_VARS => 'ARRAY', EXCLUDE_EXT => 'ARRAY', EXE_FILES => 'ARRAY', FUNCLIST => 'ARRAY', H => 'ARRAY', IMPORTS => 'HASH', INCLUDE_EXT => 'ARRAY', LIBS => ['ARRAY',''], MAN1PODS => 'HASH', MAN3PODS => 'HASH', META_ADD => 'HASH', META_MERGE => 'HASH', OBJECT => ['ARRAY', ''], PL_FILES => 'HASH', PM => 'HASH', PMLIBDIRS => 'ARRAY', PMLIBPARENTDIRS => 'ARRAY', PREREQ_PM => 'HASH', BUILD_REQUIRES => 'HASH', CONFIGURE_REQUIRES => 'HASH', TEST_REQUIRES => 'HASH', SKIP => 'ARRAY', TYPEMAPS => 'ARRAY', XS => 'HASH', XSBUILD => 'HASH', VERSION => ['version',''], _KEEP_AFTER_FLUSH => '', clean => 'HASH', depend => 'HASH', dist => 'HASH', dynamic_lib=> 'HASH', linkext => 'HASH', macro => 'HASH', postamble => 'HASH', realclean => 'HASH', test => 'HASH', tool_autosplit => 'HASH', ); @Att_Sigs{keys %Recognized_Att_Keys} = ('') x keys %Recognized_Att_Keys; @Att_Sigs{keys %Special_Sigs} = values %Special_Sigs; sub _convert_compat_attrs { #result of running several times should be same my($att) = @_; if (exists $att->{AUTHOR}) { if ($att->{AUTHOR}) { if (!ref($att->{AUTHOR})) { my $t = $att->{AUTHOR}; $att->{AUTHOR} = [$t]; } } else { $att->{AUTHOR} = []; } } } sub _verify_att { my($att) = @_; foreach my $key (sort keys %$att) { my $val = $att->{$key}; my $sig = $Att_Sigs{$key}; unless( defined $sig ) { warn "WARNING: $key is not a known parameter.\n"; next; } my @sigs = ref $sig ? @$sig : $sig; my $given = ref $val; unless( grep { _is_of_type($val, $_) } @sigs ) { my $takes = join " or ", map { _format_att($_) } @sigs; my $has = _format_att($given); warn "WARNING: $key takes a $takes not a $has.\n". " Please inform the author.\n"; } } } # Check if a given thing is a reference or instance of $type sub _is_of_type { my($thing, $type) = @_; return 1 if ref $thing eq $type; local $SIG{__DIE__}; return 1 if eval{ $thing->isa($type) }; return 0; } sub _format_att { my $given = shift; return $given eq '' ? "string/number" : uc $given eq $given ? "$given reference" : "$given object" ; } sub prompt ($;$) { ## no critic my($mess, $def) = @_; confess("prompt function called without an argument") unless defined $mess; my $isa_tty = -t STDIN && (-t STDOUT || !(-f STDOUT || -c STDOUT)) ; my $dispdef = defined $def ? "[$def] " : " "; $def = defined $def ? $def : ""; local $|=1; local $\; print "$mess $dispdef"; my $ans; if ($ENV{PERL_MM_USE_DEFAULT} || (!$isa_tty && eof STDIN)) { print "$def\n"; } else { $ans = ; if( defined $ans ) { $ans =~ s{\015?\012$}{}; } else { # user hit ctrl-D print "\n"; } } return (!defined $ans || $ans eq '') ? $def : $ans; } sub os_unsupported { die "OS unsupported\n"; } sub eval_in_subdirs { my($self) = @_; use Cwd qw(cwd abs_path); my $pwd = cwd() || die "Can't figure out your cwd!"; local @INC = map eval {abs_path($_) if -e} || $_, @INC; push @INC, '.'; # '.' has to always be at the end of @INC foreach my $dir (@{$self->{DIR}}){ my($abs) = $self->catdir($pwd,$dir); eval { $self->eval_in_x($abs); }; last if $@; } chdir $pwd; die $@ if $@; } sub eval_in_x { my($self,$dir) = @_; chdir $dir or carp("Couldn't change to directory $dir: $!"); { package main; do './Makefile.PL'; }; if ($@) { # if ($@ =~ /prerequisites/) { # die "MakeMaker WARNING: $@"; # } else { # warn "WARNING from evaluation of $dir/Makefile.PL: $@"; # } die "ERROR from evaluation of $dir/Makefile.PL: $@"; } } # package name for the classes into which the first object will be blessed my $PACKNAME = 'PACK000'; sub full_setup { $Verbose ||= 0; my @dep_macros = qw/ PERL_INCDEP PERL_ARCHLIBDEP PERL_ARCHIVEDEP /; my @fs_macros = qw/ FULLPERL XSUBPPDIR INST_ARCHLIB INST_SCRIPT INST_BIN INST_LIB INST_MAN1DIR INST_MAN3DIR INSTALLDIRS DESTDIR PREFIX INSTALL_BASE PERLPREFIX SITEPREFIX VENDORPREFIX INSTALLPRIVLIB INSTALLSITELIB INSTALLVENDORLIB INSTALLARCHLIB INSTALLSITEARCH INSTALLVENDORARCH INSTALLBIN INSTALLSITEBIN INSTALLVENDORBIN INSTALLMAN1DIR INSTALLMAN3DIR INSTALLSITEMAN1DIR INSTALLSITEMAN3DIR INSTALLVENDORMAN1DIR INSTALLVENDORMAN3DIR INSTALLSCRIPT INSTALLSITESCRIPT INSTALLVENDORSCRIPT PERL_LIB PERL_ARCHLIB SITELIBEXP SITEARCHEXP MAKE LIBPERL_A LIB PERL_SRC PERL_INC PPM_INSTALL_EXEC PPM_UNINSTALL_EXEC PPM_INSTALL_SCRIPT PPM_UNINSTALL_SCRIPT /; my @attrib_help = qw/ AUTHOR ABSTRACT ABSTRACT_FROM BINARY_LOCATION C CAPI CCFLAGS CONFIG CONFIGURE DEFINE DIR DISTNAME DISTVNAME DL_FUNCS DL_VARS EXCLUDE_EXT EXE_FILES FIRST_MAKEFILE FULLPERLRUN FULLPERLRUNINST FUNCLIST H IMPORTS INC INCLUDE_EXT LDFROM LIBS LICENSE LINKTYPE MAKEAPERL MAKEFILE MAKEFILE_OLD MAN1PODS MAN3PODS MAP_TARGET META_ADD META_MERGE MIN_PERL_VERSION BUILD_REQUIRES CONFIGURE_REQUIRES MYEXTLIB NAME NEEDS_LINKING NOECHO NO_META NO_MYMETA NO_PACKLIST NO_PERLLOCAL NORECURS NO_VC OBJECT OPTIMIZE PERL_MALLOC_OK PERL PERLMAINCC PERLRUN PERLRUNINST PERL_CORE PERM_DIR PERM_RW PERM_RWX MAGICXS PL_FILES PM PM_FILTER PMLIBDIRS PMLIBPARENTDIRS POLLUTE PREREQ_FATAL PREREQ_PM PREREQ_PRINT PRINT_PREREQ SIGN SKIP TEST_REQUIRES TYPEMAPS UNINST VERSION VERSION_FROM XS XSBUILD XSMULTI XSOPT XSPROTOARG XS_VERSION clean depend dist dynamic_lib linkext macro realclean tool_autosplit MAN1EXT MAN3EXT MACPERL_SRC MACPERL_LIB MACLIBS_68K MACLIBS_PPC MACLIBS_SC MACLIBS_MRC MACLIBS_ALL_68K MACLIBS_ALL_PPC MACLIBS_SHARED /; push @attrib_help, @fs_macros; @macro_fsentity{@fs_macros, @dep_macros} = (1) x (@fs_macros+@dep_macros); @macro_dep{@dep_macros} = (1) x @dep_macros; # IMPORTS is used under OS/2 and Win32 # @Overridable is close to @MM_Sections but not identical. The # order is important. Many subroutines declare macros. These # depend on each other. Let's try to collect the macros up front, # then pasthru, then the rules. # MM_Sections are the sections we have to call explicitly # in Overridable we have subroutines that are used indirectly @MM_Sections = qw( post_initialize const_config constants platform_constants tool_autosplit tool_xsubpp tools_other makemakerdflt dist macro depend cflags const_loadlibs const_cccmd post_constants pasthru special_targets c_o xs_c xs_o top_targets blibdirs linkext dlsyms dynamic_bs dynamic dynamic_lib static static_lib manifypods processPL installbin subdirs clean_subdirs clean realclean_subdirs realclean metafile signature dist_basics dist_core distdir dist_test dist_ci distmeta distsignature install force perldepend makefile staticmake test ppd ); # loses section ordering @Overridable = @MM_Sections; push @Overridable, qw[ libscan makeaperl needs_linking subdir_x test_via_harness test_via_script init_VERSION init_dist init_INST init_INSTALL init_DEST init_dirscan init_PM init_MANPODS init_xs init_PERL init_DIRFILESEP init_linker ]; push @MM_Sections, qw[ pm_to_blib selfdocument ]; # Postamble needs to be the last that was always the case push @MM_Sections, "postamble"; push @Overridable, "postamble"; # All sections are valid keys. @Recognized_Att_Keys{@MM_Sections} = (1) x @MM_Sections; # we will use all these variables in the Makefile @Get_from_Config = qw( ar cc cccdlflags ccdlflags dlext dlsrc exe_ext full_ar ld lddlflags ldflags libc lib_ext obj_ext osname osvers ranlib sitelibexp sitearchexp so ); # 5.5.3 doesn't have any concept of vendor libs push @Get_from_Config, qw( vendorarchexp vendorlibexp ) if $] >= 5.006; foreach my $item (@attrib_help){ $Recognized_Att_Keys{$item} = 1; } foreach my $item (@Get_from_Config) { $Recognized_Att_Keys{uc $item} = $Config{$item}; print "Attribute '\U$item\E' => '$Config{$item}'\n" if ($Verbose >= 2); } # # When we eval a Makefile.PL in a subdirectory, that one will ask # us (the parent) for the values and will prepend "..", so that # all files to be installed end up below OUR ./blib # @Prepend_parent = qw( INST_BIN INST_LIB INST_ARCHLIB INST_SCRIPT MAP_TARGET INST_MAN1DIR INST_MAN3DIR PERL_SRC PERL FULLPERL ); } sub _has_cpan_meta_requirements { return eval { require CPAN::Meta::Requirements; CPAN::Meta::Requirements->VERSION(2.130); require B; # CMR requires this, for core we have to too. }; } sub new { my($class,$self) = @_; my($key); _convert_compat_attrs($self) if defined $self && $self; # Store the original args passed to WriteMakefile() foreach my $k (keys %$self) { $self->{ARGS}{$k} = $self->{$k}; } $self = {} unless defined $self; # Temporarily bless it into MM so it can be used as an # object. It will be blessed into a temp package later. bless $self, "MM"; # Cleanup all the module requirement bits my %key2cmr; for my $key (qw(PREREQ_PM BUILD_REQUIRES CONFIGURE_REQUIRES TEST_REQUIRES)) { $self->{$key} ||= {}; if (_has_cpan_meta_requirements) { my $cmr = CPAN::Meta::Requirements->from_string_hash( $self->{$key}, { bad_version_hook => sub { #no warnings 'numeric'; # module doesn't use warnings my $fallback; if ( $_[0] =~ m!^[-+]?[0-9]*\.?[0-9]+([eE][-+]?[0-9]+)?$! ) { $fallback = sprintf "%f", $_[0]; } else { ($fallback) = $_[0] ? ($_[0] =~ /^([0-9.]+)/) : 0; $fallback += 0; carp "Unparsable version '$_[0]' for prerequisite $_[1] treated as $fallback"; } version->new($fallback); }, }, ); $self->{$key} = $cmr->as_string_hash; $key2cmr{$key} = $cmr; } else { for my $module (sort keys %{ $self->{$key} }) { my $version = $self->{$key}->{$module}; my $fallback = 0; if (!defined($version) or !length($version)) { carp "Undefined requirement for $module treated as '0' (CPAN::Meta::Requirements not available)"; } elsif ($version =~ /^\d+(?:\.\d+(?:_\d+)*)?$/) { next; } else { if ( $version =~ m!^[-+]?[0-9]*\.?[0-9]+([eE][-+]?[0-9]+)?$! ) { $fallback = sprintf "%f", $version; } else { ($fallback) = $version ? ($version =~ /^([0-9.]+)/) : 0; $fallback += 0; carp "Unparsable version '$version' for prerequisite $module treated as $fallback (CPAN::Meta::Requirements not available)"; } } $self->{$key}->{$module} = $fallback; } } } if ("@ARGV" =~ /\bPREREQ_PRINT\b/) { $self->_PREREQ_PRINT; } # PRINT_PREREQ is RedHatism. if ("@ARGV" =~ /\bPRINT_PREREQ\b/) { $self->_PRINT_PREREQ; } print "MakeMaker (v$VERSION)\n" if $Verbose; if (-f "MANIFEST" && ! -f "Makefile" && ! $UNDER_CORE){ check_manifest(); } check_hints($self); if ( defined $self->{MIN_PERL_VERSION} && $self->{MIN_PERL_VERSION} !~ /^v?[\d_\.]+$/ ) { require version; my $normal = eval { local $SIG{__WARN__} = sub { # simulate "use warnings FATAL => 'all'" for vintage perls die @_; }; version->new( $self->{MIN_PERL_VERSION} ) }; $self->{MIN_PERL_VERSION} = $normal if defined $normal && !$@; } # Translate X.Y.Z to X.00Y00Z if( defined $self->{MIN_PERL_VERSION} ) { $self->{MIN_PERL_VERSION} =~ s{ ^v? (\d+) \. (\d+) \. (\d+) $ } {sprintf "%d.%03d%03d", $1, $2, $3}ex; } my $perl_version_ok = eval { local $SIG{__WARN__} = sub { # simulate "use warnings FATAL => 'all'" for vintage perls die @_; }; !$self->{MIN_PERL_VERSION} or $self->{MIN_PERL_VERSION} <= $] }; if (!$perl_version_ok) { if (!defined $perl_version_ok) { die <<'END'; Warning: MIN_PERL_VERSION is not in a recognized format. Recommended is a quoted numerical value like '5.005' or '5.008001'. END } elsif ($self->{PREREQ_FATAL}) { die sprintf <<"END", $self->{MIN_PERL_VERSION}, $]; MakeMaker FATAL: perl version too low for this distribution. Required is %s. We run %s. END } else { warn sprintf "Warning: Perl version %s or higher required. We run %s.\n", $self->{MIN_PERL_VERSION}, $]; } } my %configure_att; # record &{$self->{CONFIGURE}} attributes my(%initial_att) = %$self; # record initial attributes my(%unsatisfied) = (); my %prereq2version; my $cmr; if (_has_cpan_meta_requirements) { $cmr = CPAN::Meta::Requirements->new; for my $key (qw(PREREQ_PM BUILD_REQUIRES CONFIGURE_REQUIRES TEST_REQUIRES)) { $cmr->add_requirements($key2cmr{$key}) if $key2cmr{$key}; } foreach my $prereq ($cmr->required_modules) { $prereq2version{$prereq} = $cmr->requirements_for_module($prereq); } } else { for my $key (qw(PREREQ_PM BUILD_REQUIRES CONFIGURE_REQUIRES TEST_REQUIRES)) { next unless my $module2version = $self->{$key}; $prereq2version{$_} = $module2version->{$_} for keys %$module2version; } } foreach my $prereq (sort keys %prereq2version) { my $required_version = $prereq2version{$prereq}; my $pr_version = 0; my $installed_file; if ( $prereq eq 'perl' ) { if ( defined $required_version && $required_version =~ /^v?[\d_\.]+$/ || $required_version !~ /^v?[\d_\.]+$/ ) { require version; my $normal = eval { version->new( $required_version ) }; $required_version = $normal if defined $normal; } $installed_file = $prereq; $pr_version = $]; } else { $installed_file = MM->_installed_file_for_module($prereq); $pr_version = MM->parse_version($installed_file) if $installed_file; $pr_version = 0 if $pr_version eq 'undef'; if ( !eval { version->new( $pr_version ); 1 } ) { #no warnings 'numeric'; # module doesn't use warnings my $fallback; if ( $pr_version =~ m!^[-+]?[0-9]*\.?[0-9]+([eE][-+]?[0-9]+)?$! ) { $fallback = sprintf '%f', $pr_version; } else { ($fallback) = $pr_version ? ($pr_version =~ /^([0-9.]+)/) : 0; $fallback += 0; carp "Unparsable version '$pr_version' for installed prerequisite $prereq treated as $fallback"; } $pr_version = $fallback; } } # convert X.Y_Z alpha version #s to X.YZ for easier comparisons $pr_version =~ s/(\d+)\.(\d+)_(\d+)/$1.$2$3/; if (!$installed_file) { warn sprintf "Warning: prerequisite %s %s not found.\n", $prereq, $required_version unless $self->{PREREQ_FATAL} or $UNDER_CORE; $unsatisfied{$prereq} = 'not installed'; } elsif ( $cmr ? !$cmr->accepts_module($prereq, $pr_version) : $required_version > $pr_version ) { warn sprintf "Warning: prerequisite %s %s not found. We have %s.\n", $prereq, $required_version, ($pr_version || 'unknown version') unless $self->{PREREQ_FATAL} or $UNDER_CORE; $unsatisfied{$prereq} = $required_version || 'unknown version' ; } } if (%unsatisfied && $self->{PREREQ_FATAL}){ my $failedprereqs = join "\n", map {" $_ $unsatisfied{$_}"} sort { $a cmp $b } keys %unsatisfied; die <<"END"; MakeMaker FATAL: prerequisites not found. $failedprereqs Please install these modules first and rerun 'perl Makefile.PL'. END } if (defined $self->{CONFIGURE}) { if (ref $self->{CONFIGURE} eq 'CODE') { %configure_att = %{&{$self->{CONFIGURE}}}; _convert_compat_attrs(\%configure_att); $self = { %$self, %configure_att }; } else { croak "Attribute 'CONFIGURE' to WriteMakefile() not a code reference\n"; } } my $newclass = ++$PACKNAME; local @Parent = @Parent; # Protect against non-local exits { print "Blessing Object into class [$newclass]\n" if $Verbose>=2; mv_all_methods("MY",$newclass); bless $self, $newclass; push @Parent, $self; require ExtUtils::MY; no strict 'refs'; ## no critic; @{"$newclass\:\:ISA"} = 'MM'; } if (defined $Parent[-2]){ $self->{PARENT} = $Parent[-2]; for my $key (@Prepend_parent) { next unless defined $self->{PARENT}{$key}; # Don't stomp on WriteMakefile() args. next if defined $self->{ARGS}{$key} and $self->{ARGS}{$key} eq $self->{$key}; $self->{$key} = $self->{PARENT}{$key}; if ($Is_VMS && $key =~ /PERL$/) { # PERL or FULLPERL will be a command verb or even a # command with an argument instead of a full file # specification under VMS. So, don't turn the command # into a filespec, but do add a level to the path of # the argument if not already absolute. my @cmd = split /\s+/, $self->{$key}; $cmd[1] = $self->catfile('[-]',$cmd[1]) unless (@cmd < 2) || $self->file_name_is_absolute($cmd[1]); $self->{$key} = join(' ', @cmd); } else { my $value = $self->{$key}; # not going to test in FS so only stripping start $value =~ s/^"// if $key =~ /PERL$/; $value = $self->catdir("..", $value) unless $self->file_name_is_absolute($value); $value = qq{"$value} if $key =~ /PERL$/; $self->{$key} = $value; } } if ($self->{PARENT}) { $self->{PARENT}->{CHILDREN}->{$newclass} = $self; foreach my $opt (qw(POLLUTE PERL_CORE LINKTYPE LD OPTIMIZE)) { if (exists $self->{PARENT}->{$opt} and not exists $self->{$opt}) { # inherit, but only if already unspecified $self->{$opt} = $self->{PARENT}->{$opt}; } } } my @fm = grep /^FIRST_MAKEFILE=/, @ARGV; parse_args($self,@fm) if @fm; } else { parse_args($self, _shellwords($ENV{PERL_MM_OPT} || ''),@ARGV); } # RT#91540 PREREQ_FATAL not recognized on command line if (%unsatisfied && $self->{PREREQ_FATAL}){ my $failedprereqs = join "\n", map {" $_ $unsatisfied{$_}"} sort { $a cmp $b } keys %unsatisfied; die <<"END"; MakeMaker FATAL: prerequisites not found. $failedprereqs Please install these modules first and rerun 'perl Makefile.PL'. END } $self->{NAME} ||= $self->guess_name; warn "Warning: NAME must be a package name\n" unless $self->{NAME} =~ m!^[A-Z_a-z][0-9A-Z_a-z]*(?:::[0-9A-Z_a-z]+)*$!; ($self->{NAME_SYM} = $self->{NAME}) =~ s/\W+/_/g; $self->init_MAKE; $self->init_main; $self->init_VERSION; $self->init_dist; $self->init_INST; $self->init_INSTALL; $self->init_DEST; $self->init_dirscan; $self->init_PM; $self->init_MANPODS; $self->init_xs; $self->init_PERL; $self->init_DIRFILESEP; $self->init_linker; $self->init_ABSTRACT; $self->arch_check( $INC{'Config.pm'}, $self->catfile($Config{'archlibexp'}, "Config.pm") ); $self->init_tools(); $self->init_others(); $self->init_platform(); $self->init_PERM(); my @args = @ARGV; @args = map { Encode::decode(locale => $_) } @args if $CAN_DECODE; my($argv) = neatvalue(\@args); $argv =~ s/^\[/(/; $argv =~ s/\]$/)/; push @{$self->{RESULT}}, <{NAME} extension to perl. # # It was generated automatically by MakeMaker version # $VERSION (Revision: $Revision) from the contents of # Makefile.PL. Don't edit this file, edit Makefile.PL instead. # # ANY CHANGES MADE HERE WILL BE LOST! # # MakeMaker ARGV: $argv # END push @{$self->{RESULT}}, $self->_MakeMaker_Parameters_section(\%initial_att); if (defined $self->{CONFIGURE}) { push @{$self->{RESULT}}, < 0) { foreach my $key (sort keys %configure_att){ next if $key eq 'ARGS'; my($v) = neatvalue($configure_att{$key}); $v =~ s/(CODE|HASH|ARRAY|SCALAR)\([\dxa-f]+\)/$1\(...\)/; $v =~ tr/\n/ /s; push @{$self->{RESULT}}, "# $key => $v"; } } else { push @{$self->{RESULT}}, "# no values returned"; } undef %configure_att; # free memory } # turn the SKIP array into a SKIPHASH hash for my $skip (@{$self->{SKIP} || []}) { $self->{SKIPHASH}{$skip} = 1; } delete $self->{SKIP}; # free memory if ($self->{PARENT}) { for (qw/install dist dist_basics dist_core distdir dist_test dist_ci/) { $self->{SKIPHASH}{$_} = 1; } } # We run all the subdirectories now. They don't have much to query # from the parent, but the parent has to query them: if they need linking! unless ($self->{NORECURS}) { $self->eval_in_subdirs if @{$self->{DIR}}; } foreach my $section ( @MM_Sections ){ # Support for new foo_target() methods. my $method = $section; $method .= '_target' unless $self->can($method); print "Processing Makefile '$section' section\n" if ($Verbose >= 2); my($skipit) = $self->skipcheck($section); if ($skipit){ push @{$self->{RESULT}}, "\n# --- MakeMaker $section section $skipit."; } else { my(%a) = %{$self->{$section} || {}}; push @{$self->{RESULT}}, "\n# --- MakeMaker $section section:"; push @{$self->{RESULT}}, "# " . join ", ", %a if $Verbose && %a; push @{$self->{RESULT}}, $self->maketext_filter( $self->$method( %a ) ); } } push @{$self->{RESULT}}, "\n# End."; $self; } sub WriteEmptyMakefile { croak "WriteEmptyMakefile: Need an even number of args" if @_ % 2; my %att = @_; $att{DIR} = [] unless $att{DIR}; # don't recurse by default my $self = MM->new(\%att); my $new = $self->{MAKEFILE}; my $old = $self->{MAKEFILE_OLD}; if (-f $old) { _unlink($old) or warn "unlink $old: $!"; } if ( -f $new ) { _rename($new, $old) or warn "rename $new => $old: $!" } open my $mfh, '>', $new or die "open $new for write: $!"; print $mfh <<'EOP'; all : manifypods : subdirs : dynamic : static : clean : install : makemakerdflt : test : test_dynamic : test_static : EOP close $mfh or die "close $new for write: $!"; } sub _installed_file_for_module { my $class = shift; my $prereq = shift; my $file = "$prereq.pm"; $file =~ s{::}{/}g; my $path; for my $dir (@INC) { my $tmp = File::Spec->catfile($dir, $file); if ( -r $tmp ) { $path = $tmp; last; } } return $path; } # Extracted from MakeMaker->new so we can test it sub _MakeMaker_Parameters_section { my $self = shift; my $att = shift; my @result = <<'END'; # MakeMaker Parameters: END foreach my $key (sort keys %$att){ next if $key eq 'ARGS'; my $v; if ($key eq 'PREREQ_PM') { # CPAN.pm takes prereqs from this field in 'Makefile' # and does not know about BUILD_REQUIRES $v = neatvalue({ %{ $att->{PREREQ_PM} || {} }, %{ $att->{BUILD_REQUIRES} || {} }, %{ $att->{TEST_REQUIRES} || {} }, }); } else { $v = neatvalue($att->{$key}); } $v =~ s/(CODE|HASH|ARRAY|SCALAR)\([\dxa-f]+\)/$1\(...\)/; $v =~ tr/\n/ /s; push @result, "# $key => $v"; } return @result; } # _shellwords and _parseline borrowed from Text::ParseWords sub _shellwords { my (@lines) = @_; my @allwords; foreach my $line (@lines) { $line =~ s/^\s+//; my @words = _parse_line('\s+', 0, $line); pop @words if (@words and !defined $words[-1]); return() unless (@words || !length($line)); push(@allwords, @words); } return(@allwords); } sub _parse_line { my($delimiter, $keep, $line) = @_; my($word, @pieces); no warnings 'uninitialized'; # we will be testing undef strings while (length($line)) { # This pattern is optimised to be stack conservative on older perls. # Do not refactor without being careful and testing it on very long strings. # See Perl bug #42980 for an example of a stack busting input. $line =~ s/^ (?: # double quoted string (") # $quote ((?>[^\\"]*(?:\\.[^\\"]*)*))" # $quoted | # --OR-- # singe quoted string (') # $quote ((?>[^\\']*(?:\\.[^\\']*)*))' # $quoted | # --OR-- # unquoted string ( # $unquoted (?:\\.|[^\\"'])*? ) # followed by ( # $delim \Z(?!\n) # EOL | # --OR-- (?-x:$delimiter) # delimiter | # --OR-- (?!^)(?=["']) # a quote ) )//xs or return; # extended layout my ($quote, $quoted, $unquoted, $delim) = (($1 ? ($1,$2) : ($3,$4)), $5, $6); return() unless( defined($quote) || length($unquoted) || length($delim)); if ($keep) { $quoted = "$quote$quoted$quote"; } else { $unquoted =~ s/\\(.)/$1/sg; if (defined $quote) { $quoted =~ s/\\(.)/$1/sg if ($quote eq '"'); #$quoted =~ s/\\([\\'])/$1/g if ( $PERL_SINGLE_QUOTE && $quote eq "'"); } } $word .= substr($line, 0, 0); # leave results tainted $word .= defined $quote ? $quoted : $unquoted; if (length($delim)) { push(@pieces, $word); push(@pieces, $delim) if ($keep eq 'delimiters'); undef $word; } if (!length($line)) { push(@pieces, $word); } } return(@pieces); } sub check_manifest { print "Checking if your kit is complete...\n"; require ExtUtils::Manifest; # avoid warning $ExtUtils::Manifest::Quiet = $ExtUtils::Manifest::Quiet = 1; my(@missed) = ExtUtils::Manifest::manicheck(); if (@missed) { print "Warning: the following files are missing in your kit:\n"; print "\t", join "\n\t", @missed; print "\n"; print "Please inform the author.\n"; } else { print "Looks good\n"; } } sub parse_args{ my($self, @args) = @_; @args = map { Encode::decode(locale => $_) } @args if $CAN_DECODE; foreach (@args) { unless (m/(.*?)=(.*)/) { ++$Verbose if m/^verb/; next; } my($name, $value) = ($1, $2); if ($value =~ m/^~(\w+)?/) { # tilde with optional username $value =~ s [^~(\w*)] [$1 ? ((getpwnam($1))[7] || "~$1") : (getpwuid($>))[7] ]ex; } # Remember the original args passed it. It will be useful later. $self->{ARGS}{uc $name} = $self->{uc $name} = $value; } # catch old-style 'potential_libs' and inform user how to 'upgrade' if (defined $self->{potential_libs}){ my($msg)="'potential_libs' => '$self->{potential_libs}' should be"; if ($self->{potential_libs}){ print "$msg changed to:\n\t'LIBS' => ['$self->{potential_libs}']\n"; } else { print "$msg deleted.\n"; } $self->{LIBS} = [$self->{potential_libs}]; delete $self->{potential_libs}; } # catch old-style 'ARMAYBE' and inform user how to 'upgrade' if (defined $self->{ARMAYBE}){ my($armaybe) = $self->{ARMAYBE}; print "ARMAYBE => '$armaybe' should be changed to:\n", "\t'dynamic_lib' => {ARMAYBE => '$armaybe'}\n"; my(%dl) = %{$self->{dynamic_lib} || {}}; $self->{dynamic_lib} = { %dl, ARMAYBE => $armaybe}; delete $self->{ARMAYBE}; } if (defined $self->{LDTARGET}){ print "LDTARGET should be changed to LDFROM\n"; $self->{LDFROM} = $self->{LDTARGET}; delete $self->{LDTARGET}; } # Turn a DIR argument on the command line into an array if (defined $self->{DIR} && ref \$self->{DIR} eq 'SCALAR') { # So they can choose from the command line, which extensions they want # the grep enables them to have some colons too much in case they # have to build a list with the shell $self->{DIR} = [grep $_, split ":", $self->{DIR}]; } # Turn a INCLUDE_EXT argument on the command line into an array if (defined $self->{INCLUDE_EXT} && ref \$self->{INCLUDE_EXT} eq 'SCALAR') { $self->{INCLUDE_EXT} = [grep $_, split '\s+', $self->{INCLUDE_EXT}]; } # Turn a EXCLUDE_EXT argument on the command line into an array if (defined $self->{EXCLUDE_EXT} && ref \$self->{EXCLUDE_EXT} eq 'SCALAR') { $self->{EXCLUDE_EXT} = [grep $_, split '\s+', $self->{EXCLUDE_EXT}]; } foreach my $mmkey (sort keys %$self){ next if $mmkey eq 'ARGS'; print " $mmkey => ", neatvalue($self->{$mmkey}), "\n" if $Verbose; print "'$mmkey' is not a known MakeMaker parameter name.\n" unless exists $Recognized_Att_Keys{$mmkey}; } $| = 1 if $Verbose; } sub check_hints { my($self) = @_; # We allow extension-specific hints files. require File::Spec; my $curdir = File::Spec->curdir; my $hint_dir = File::Spec->catdir($curdir, "hints"); return unless -d $hint_dir; # First we look for the best hintsfile we have my($hint)="${^O}_$Config{osvers}"; $hint =~ s/\./_/g; $hint =~ s/_$//; return unless $hint; # Also try without trailing minor version numbers. while (1) { last if -f File::Spec->catfile($hint_dir, "$hint.pl"); # found } continue { last unless $hint =~ s/_[^_]*$//; # nothing to cut off } my $hint_file = File::Spec->catfile($hint_dir, "$hint.pl"); return unless -f $hint_file; # really there _run_hintfile($self, $hint_file); } sub _run_hintfile { our $self; local($self) = shift; # make $self available to the hint file. my($hint_file) = shift; local($@, $!); print "Processing hints file $hint_file\n" if $Verbose; # Just in case the ./ isn't on the hint file, which File::Spec can # often strip off, we bung the curdir into @INC local @INC = (File::Spec->curdir, @INC); my $ret = do $hint_file; if( !defined $ret ) { my $error = $@ || $!; warn $error; } } sub mv_all_methods { my($from,$to) = @_; local $SIG{__WARN__} = sub { # can't use 'no warnings redefined', 5.6 only warn @_ unless $_[0] =~ /^Subroutine .* redefined/ }; foreach my $method (@Overridable) { next unless defined &{"${from}::$method"}; no strict 'refs'; ## no critic *{"${to}::$method"} = \&{"${from}::$method"}; # If we delete a method, then it will be undefined and cannot # be called. But as long as we have Makefile.PLs that rely on # %MY:: being intact, we have to fill the hole with an # inheriting method: { package MY; my $super = "SUPER::".$method; *{$method} = sub { shift->$super(@_); }; } } } sub skipcheck { my($self) = shift; my($section) = @_; return 'skipped' if $section eq 'metafile' && $UNDER_CORE; if ($section eq 'dynamic') { print "Warning (non-fatal): Target 'dynamic' depends on targets ", "in skipped section 'dynamic_bs'\n" if $self->{SKIPHASH}{dynamic_bs} && $Verbose; print "Warning (non-fatal): Target 'dynamic' depends on targets ", "in skipped section 'dynamic_lib'\n" if $self->{SKIPHASH}{dynamic_lib} && $Verbose; } if ($section eq 'dynamic_lib') { print "Warning (non-fatal): Target '\$(INST_DYNAMIC)' depends on ", "targets in skipped section 'dynamic_bs'\n" if $self->{SKIPHASH}{dynamic_bs} && $Verbose; } if ($section eq 'static') { print "Warning (non-fatal): Target 'static' depends on targets ", "in skipped section 'static_lib'\n" if $self->{SKIPHASH}{static_lib} && $Verbose; } return 'skipped' if $self->{SKIPHASH}{$section}; return ''; } # returns filehandle, dies on fail. :raw so no :crlf sub open_for_writing { my ($file) = @_; open my $fh ,">", $file or die "Unable to open $file: $!"; my @layers = ':raw'; push @layers, join ' ', ':encoding(locale)' if $CAN_DECODE; binmode $fh, join ' ', @layers; $fh; } sub flush { my $self = shift; my $finalname = $self->{MAKEFILE}; printf "Generating a %s %s\n", $self->make_type, $finalname if $Verbose || !$self->{PARENT}; print "Writing $finalname for $self->{NAME}\n" if $Verbose || !$self->{PARENT}; unlink($finalname, "MakeMaker.tmp", $Is_VMS ? 'Descrip.MMS' : ()); write_file_via_tmp($finalname, $self->{RESULT}); # Write MYMETA.yml to communicate metadata up to the CPAN clients print "Writing MYMETA.yml and MYMETA.json\n" if !$self->{NO_MYMETA} and $self->write_mymeta( $self->mymeta ); # save memory if ($self->{PARENT} && !$self->{_KEEP_AFTER_FLUSH}) { my %keep = map { ($_ => 1) } qw(NEEDS_LINKING HAS_LINK_CODE); delete $self->{$_} for grep !$keep{$_}, keys %$self; } system("$Config::Config{eunicefix} $finalname") if $Config::Config{eunicefix} ne ":"; return; } sub write_file_via_tmp { my ($finalname, $contents) = @_; my $fh = open_for_writing("MakeMaker.tmp"); die "write_file_via_tmp: 2nd arg must be ref" unless ref $contents; for my $chunk (@$contents) { my $to_write = $chunk; utf8::encode $to_write if !$CAN_DECODE && $] > 5.008; print $fh "$to_write\n" or die "Can't write to MakeMaker.tmp: $!"; } close $fh or die "Can't write to MakeMaker.tmp: $!"; _rename("MakeMaker.tmp", $finalname) or warn "rename MakeMaker.tmp => $finalname: $!"; chmod 0644, $finalname if !$Is_VMS; return; } # This is a rename for OS's where the target must be unlinked first. sub _rename { my($src, $dest) = @_; _unlink($dest); return rename $src, $dest; } # This is an unlink for OS's where the target must be writable first. sub _unlink { my @files = @_; chmod 0666, @files; return unlink @files; } # The following mkbootstrap() is only for installations that are calling # the pre-4.1 mkbootstrap() from their old Makefiles. This MakeMaker # writes Makefiles, that use ExtUtils::Mkbootstrap directly. sub mkbootstrap { die <".neatvalue($v->{$key}); } return "{ ".join(', ',@m)." }"; } sub _find_magic_vstring { my $value = shift; return $value if $UNDER_CORE; my $tvalue = ''; require B; my $sv = B::svref_2object(\$value); my $magic = ref($sv) eq 'B::PVMG' ? $sv->MAGIC : undef; while ( $magic ) { if ( $magic->TYPE eq 'V' ) { $tvalue = $magic->PTR; $tvalue =~ s/^v?(.+)$/v$1/; last; } else { $magic = $magic->MOREMAGIC; } } return $tvalue; } sub selfdocument { my($self) = @_; my(@m); if ($Verbose){ push @m, "\n# Full list of MakeMaker attribute values:"; foreach my $key (sort keys %$self){ next if $key eq 'RESULT' || $key =~ /^[A-Z][a-z]/; my($v) = neatvalue($self->{$key}); $v =~ s/(CODE|HASH|ARRAY|SCALAR)\([\dxa-f]+\)/$1\(...\)/; $v =~ tr/\n/ /s; push @m, "# $key => $v"; } } # added here as selfdocument is not overridable push @m, <<'EOF'; # here so even if top_targets is overridden, these will still be defined # gmake will silently still work if any are .PHONY-ed but nmake won't EOF push @m, join "\n", map "$_ ::\n\t\$(NOECHO) \$(NOOP)\n", # config is so manifypods won't puke if no subdirs grep !$self->{SKIPHASH}{$_}, qw(static dynamic config); join "\n", @m; } 1; __END__ package ExtUtils::Liblist; use strict; our $VERSION = '7.34'; $VERSION = eval $VERSION; use File::Spec; require ExtUtils::Liblist::Kid; our @ISA = qw(ExtUtils::Liblist::Kid File::Spec); # Backwards compatibility with old interface. sub ext { goto &ExtUtils::Liblist::Kid::ext; } sub lsdir { shift; my $rex = qr/$_[1]/; opendir my $dir_fh, $_[0]; my @out = grep /$rex/, readdir $dir_fh; closedir $dir_fh; return @out; } __END__ package ExtUtils::Installed; use 5.00503; use strict; #use warnings; # XXX requires 5.6 use Carp qw(); use ExtUtils::Packlist; use ExtUtils::MakeMaker; use Config; use File::Find; use File::Basename; use File::Spec; my $Is_VMS = $^O eq 'VMS'; my $DOSISH = ($^O =~ /^(MSWin\d\d|os2|dos|mint)$/); require VMS::Filespec if $Is_VMS; use vars qw($VERSION); $VERSION = '2.14'; $VERSION = eval $VERSION; sub _is_prefix { my ($self, $path, $prefix) = @_; return unless defined $prefix && defined $path; if( $Is_VMS ) { $prefix = VMS::Filespec::unixify($prefix); $path = VMS::Filespec::unixify($path); } # Unix path normalization. $prefix = File::Spec->canonpath($prefix); return 1 if substr($path, 0, length($prefix)) eq $prefix; if ($DOSISH) { $path =~ s|\\|/|g; $prefix =~ s|\\|/|g; return 1 if $path =~ m{^\Q$prefix\E}i; } return(0); } sub _is_doc { my ($self, $path) = @_; my $man1dir = $self->{':private:'}{Config}{man1direxp}; my $man3dir = $self->{':private:'}{Config}{man3direxp}; return(($man1dir && $self->_is_prefix($path, $man1dir)) || ($man3dir && $self->_is_prefix($path, $man3dir)) ? 1 : 0) } sub _is_type { my ($self, $path, $type) = @_; return 1 if $type eq "all"; return($self->_is_doc($path)) if $type eq "doc"; my $conf= $self->{':private:'}{Config}; if ($type eq "prog") { return($self->_is_prefix($path, $conf->{prefix} || $conf->{prefixexp}) && !($self->_is_doc($path)) ? 1 : 0); } return(0); } sub _is_under { my ($self, $path, @under) = @_; $under[0] = "" if (! @under); foreach my $dir (@under) { return(1) if ($self->_is_prefix($path, $dir)); } return(0); } sub _fix_dirs { my ($self, @dirs)= @_; # File::Find does not know how to deal with VMS filepaths. if( $Is_VMS ) { $_ = VMS::Filespec::unixify($_) for @dirs; } if ($DOSISH) { s|\\|/|g for @dirs; } return wantarray ? @dirs : $dirs[0]; } sub _make_entry { my ($self, $module, $packlist_file, $modfile)= @_; my $data= { module => $module, packlist => scalar(ExtUtils::Packlist->new($packlist_file)), packlist_file => $packlist_file, }; if (!$modfile) { $data->{version} = $self->{':private:'}{Config}{version}; } else { $data->{modfile} = $modfile; # Find the top-level module file in @INC $data->{version} = ''; foreach my $dir (@{$self->{':private:'}{INC}}) { my $p = File::Spec->catfile($dir, $modfile); if (-r $p) { $module = _module_name($p, $module) if $Is_VMS; $data->{version} = MM->parse_version($p); $data->{version_from} = $p; $data->{packlist_valid} = exists $data->{packlist}{$p}; last; } } } $self->{$module}= $data; } our $INSTALLED; sub new { my ($class) = shift(@_); $class = ref($class) || $class; my %args = @_; return $INSTALLED if $INSTALLED and ($args{default_get} || $args{default}); my $self = bless {}, $class; $INSTALLED= $self if $args{default_set} || $args{default}; if ($args{config_override}) { eval { $self->{':private:'}{Config} = { %{$args{config_override}} }; } or Carp::croak( "The 'config_override' parameter must be a hash reference." ); } else { $self->{':private:'}{Config} = \%Config; } for my $tuple ([inc_override => INC => [ @INC ] ], [ extra_libs => EXTRA => [] ]) { my ($arg,$key,$val)=@$tuple; if ( $args{$arg} ) { eval { $self->{':private:'}{$key} = [ @{$args{$arg}} ]; } or Carp::croak( "The '$arg' parameter must be an array reference." ); } elsif ($val) { $self->{':private:'}{$key} = $val; } } { my %dupe; @{$self->{':private:'}{LIBDIRS}} = grep { $_ ne '.' || ! $args{skip_cwd} } grep { -e $_ && !$dupe{$_}++ } @{$self->{':private:'}{EXTRA}}, @{$self->{':private:'}{INC}}; } my @dirs= $self->_fix_dirs(@{$self->{':private:'}{LIBDIRS}}); # Read the core packlist my $archlib = $self->_fix_dirs($self->{':private:'}{Config}{archlibexp}); $self->_make_entry("Perl",File::Spec->catfile($archlib, '.packlist')); my $root; # Read the module packlists my $sub = sub { # Only process module .packlists return if $_ ne ".packlist" || $File::Find::dir eq $archlib; # Hack of the leading bits of the paths & convert to a module name my $module = $File::Find::name; my $found = $module =~ s!^.*?/auto/(.*)/.packlist!$1!s or do { # warn "Woah! \$_=$_\n\$module=$module\n\$File::Find::dir=$File::Find::dir\n", # join ("\n",@dirs); return; }; my $modfile = "$module.pm"; $module =~ s!/!::!g; return if $self->{$module}; #shadowing? $self->_make_entry($module,$File::Find::name,$modfile); }; while (@dirs) { $root= shift @dirs; next if !-d $root; find($sub,$root); } return $self; } # VMS's non-case preserving file-system means the package name can't # be reconstructed from the filename. sub _module_name { my($file, $orig_module) = @_; my $module = ''; if (open PACKFH, $file) { while () { if (/package\s+(\S+)\s*;/) { my $pack = $1; # Make a sanity check, that lower case $module # is identical to lowercase $pack before # accepting it if (lc($pack) eq lc($orig_module)) { $module = $pack; last; } } } close PACKFH; } print STDERR "Couldn't figure out the package name for $file\n" unless $module; return $module; } sub modules { my ($self) = @_; $self= $self->new(default=>1) if !ref $self; # Bug/feature of sort in scalar context requires this. return wantarray ? sort grep { not /^:private:$/ } keys %$self : grep { not /^:private:$/ } keys %$self; } sub files { my ($self, $module, $type, @under) = @_; $self= $self->new(default=>1) if !ref $self; # Validate arguments Carp::croak("$module is not installed") if (! exists($self->{$module})); $type = "all" if (! defined($type)); Carp::croak('type must be "all", "prog" or "doc"') if ($type ne "all" && $type ne "prog" && $type ne "doc"); my (@files); foreach my $file (keys(%{$self->{$module}{packlist}})) { push(@files, $file) if ($self->_is_type($file, $type) && $self->_is_under($file, @under)); } return(@files); } sub directories { my ($self, $module, $type, @under) = @_; $self= $self->new(default=>1) if !ref $self; my (%dirs); foreach my $file ($self->files($module, $type, @under)) { $dirs{dirname($file)}++; } return sort keys %dirs; } sub directory_tree { my ($self, $module, $type, @under) = @_; $self= $self->new(default=>1) if !ref $self; my (%dirs); foreach my $dir ($self->directories($module, $type, @under)) { $dirs{$dir}++; my ($last) = (""); while ($last ne $dir) { $last = $dir; $dir = dirname($dir); last if !$self->_is_under($dir, @under); $dirs{$dir}++; } } return(sort(keys(%dirs))); } sub validate { my ($self, $module, $remove) = @_; $self= $self->new(default=>1) if !ref $self; Carp::croak("$module is not installed") if (! exists($self->{$module})); return($self->{$module}{packlist}->validate($remove)); } sub packlist { my ($self, $module) = @_; $self= $self->new(default=>1) if !ref $self; Carp::croak("$module is not installed") if (! exists($self->{$module})); return($self->{$module}{packlist}); } sub version { my ($self, $module) = @_; $self= $self->new(default=>1) if !ref $self; Carp::croak("$module is not installed") if (! exists($self->{$module})); return($self->{$module}{version}); } sub debug_dump { my ($self, $module) = @_; $self= $self->new(default=>1) if !ref $self; local $self->{":private:"}{Config}; require Data::Dumper; print Data::Dumper->new([$self])->Sortkeys(1)->Indent(1)->Dump(); } 1; __END__ package ExtUtils::MM_MacOS; use strict; our $VERSION = '7.34'; $VERSION = eval $VERSION; sub new { die 'MacOS Classic (MacPerl) is no longer supported by MakeMaker'; } 1; #!./perl -w package ExtUtils::Miniperl; use strict; require Exporter; use ExtUtils::Embed 1.31, qw(xsi_header xsi_protos xsi_body); our @ISA = qw(Exporter); our @EXPORT = qw(writemain); our $VERSION = '1.08'; # blead will run this with miniperl, hence we can't use autodie or File::Temp my $temp; END { return if !defined $temp || !-e $temp; unlink $temp or warn "Can't unlink '$temp': $!"; } sub writemain{ my ($fh, $real); if (ref $_[0] eq 'SCALAR') { $real = ${+shift}; $temp = $real; $temp =~ s/(?:.c)?\z/.new/; open $fh, '>', $temp or die "Can't open '$temp' for writing: $!"; } elsif (ref $_[0]) { $fh = shift; } else { $fh = \*STDOUT; } my(@exts) = @_; printf $fh <<'EOF!HEAD', xsi_header(); /* miniperlmain.c or perlmain.c - a generated file * * Copyright (C) 1994, 1995, 1996, 1997, 1999, 2000, 2001, 2002, 2003, * 2004, 2005, 2006, 2007, 2016 by Larry Wall and others * * You may distribute under the terms of either the GNU General Public * License or the Artistic License, as specified in the README file. * */ /* * The Road goes ever on and on * Down from the door where it began. * * [Bilbo on p.35 of _The Lord of the Rings_, I/i: "A Long-Expected Party"] * [Frodo on p.73 of _The Lord of the Rings_, I/iii: "Three Is Company"] */ /* This file contains the main() function for the perl interpreter. * Note that miniperlmain.c contains main() for the 'miniperl' binary, * while perlmain.c contains main() for the 'perl' binary. The typical * difference being that the latter includes Dynaloader. * * Miniperl is like perl except that it does not support dynamic loading, * and in fact is used to build the dynamic modules needed for the 'real' * perl executable. * * The content of the body of this generated file is mostly contained * in Miniperl.pm - edit that file if you want to change anything. * miniperlmain.c is generated by running regen/miniperlmain.pl.pl, while * perlmain.c is built automatically by Makefile (so the former is * included in the tarball while the latter isn't). */ #ifdef OEMVS #ifdef MYMALLOC /* sbrk is limited to first heap segment so make it big */ #pragma runopts(HEAP(8M,500K,ANYWHERE,KEEP,8K,4K) STACK(,,ANY,) ALL31(ON)) #else #pragma runopts(HEAP(2M,500K,ANYWHERE,KEEP,8K,4K) STACK(,,ANY,) ALL31(ON)) #endif #endif #define PERL_IN_MINIPERLMAIN_C %s static void xs_init (pTHX); static PerlInterpreter *my_perl; #if defined(PERL_GLOBAL_STRUCT_PRIVATE) /* The static struct perl_vars* may seem counterproductive since the * whole idea PERL_GLOBAL_STRUCT_PRIVATE was to avoid statics, but note * that this static is not in the shared perl library, the globals PL_Vars * and PL_VarsPtr will stay away. */ static struct perl_vars* my_plvarsp; struct perl_vars* Perl_GetVarsPrivate(void) { return my_plvarsp; } #endif #ifdef NO_ENV_ARRAY_IN_MAIN extern char **environ; int main(int argc, char **argv) #else int main(int argc, char **argv, char **env) #endif { int exitstatus, i; #ifdef PERL_GLOBAL_STRUCT struct perl_vars *my_vars = init_global_struct(); # ifdef PERL_GLOBAL_STRUCT_PRIVATE int veto; my_plvarsp = my_vars; # endif #endif /* PERL_GLOBAL_STRUCT */ #ifndef NO_ENV_ARRAY_IN_MAIN PERL_UNUSED_ARG(env); #endif #ifndef PERL_USE_SAFE_PUTENV PL_use_safe_putenv = FALSE; #endif /* PERL_USE_SAFE_PUTENV */ /* if user wants control of gprof profiling off by default */ /* noop unless Configure is given -Accflags=-DPERL_GPROF_CONTROL */ PERL_GPROF_MONCONTROL(0); #ifdef NO_ENV_ARRAY_IN_MAIN PERL_SYS_INIT3(&argc,&argv,&environ); #else PERL_SYS_INIT3(&argc,&argv,&env); #endif #if defined(USE_ITHREADS) /* XXX Ideally, this should really be happening in perl_alloc() or * perl_construct() to keep libperl.a transparently fork()-safe. * It is currently done here only because Apache/mod_perl have * problems due to lack of a call to cancel pthread_atfork() * handlers when shared objects that contain the handlers may * be dlclose()d. This forces applications that embed perl to * call PTHREAD_ATFORK() explicitly, but if and only if it hasn't * been called at least once before in the current process. * --GSAR 2001-07-20 */ PTHREAD_ATFORK(Perl_atfork_lock, Perl_atfork_unlock, Perl_atfork_unlock); #endif PERL_SYS_FPU_INIT; if (!PL_do_undump) { my_perl = perl_alloc(); if (!my_perl) exit(1); perl_construct(my_perl); PL_perl_destruct_level = 0; } PL_exit_flags |= PERL_EXIT_DESTRUCT_END; if (!perl_parse(my_perl, xs_init, argc, argv, (char **)NULL)) perl_run(my_perl); #ifndef PERL_MICRO /* Unregister our signal handler before destroying my_perl */ for (i = 1; PL_sig_name[i]; i++) { if (rsignal_state(PL_sig_num[i]) == (Sighandler_t) PL_csighandlerp) { rsignal(PL_sig_num[i], (Sighandler_t) SIG_DFL); } } #endif exitstatus = perl_destruct(my_perl); perl_free(my_perl); #if defined(USE_ENVIRON_ARRAY) && defined(PERL_TRACK_MEMPOOL) && !defined(NO_ENV_ARRAY_IN_MAIN) /* * The old environment may have been freed by perl_free() * when PERL_TRACK_MEMPOOL is defined, but without having * been restored by perl_destruct() before (this is only * done if destruct_level > 0). * * It is important to have a valid environment for atexit() * routines that are eventually called. */ environ = env; #endif PERL_SYS_TERM(); #ifdef PERL_GLOBAL_STRUCT # ifdef PERL_GLOBAL_STRUCT_PRIVATE veto = my_plvarsp->Gveto_cleanup; # endif free_global_struct(my_vars); # ifdef PERL_GLOBAL_STRUCT_PRIVATE if (!veto) my_plvarsp = NULL; /* Remember, functions registered with atexit() can run after this point, and may access "global" variables, and hence end up calling Perl_GetVarsPrivate() */ #endif #endif /* PERL_GLOBAL_STRUCT */ exit(exitstatus); } /* Register any extra external extensions */ EOF!HEAD print $fh xsi_protos(@exts), <<'EOT', xsi_body(@exts), "}\n"; static void xs_init(pTHX) { EOT if ($real) { close $fh or die "Can't close '$temp': $!"; rename $temp, $real or die "Can't rename '$temp' to '$real': $!"; } } 1; __END__ # ex: set ts=8 sts=4 sw=4 et: package ExtUtils::MM_DOS; use strict; our $VERSION = '7.34'; $VERSION = eval $VERSION; require ExtUtils::MM_Any; require ExtUtils::MM_Unix; our @ISA = qw( ExtUtils::MM_Any ExtUtils::MM_Unix ); sub os_flavor { return('DOS'); } sub replace_manpage_separator { my($self, $man) = @_; $man =~ s,/+,__,g; return $man; } sub xs_static_lib_is_xs { return 1; } 1; package ExtUtils::testlib; use strict; use warnings; our $VERSION = '7.34'; $VERSION = eval $VERSION; use Cwd; use File::Spec; # So the tests can chdir around and not break @INC. # We use getcwd() because otherwise rel2abs will blow up under taint # mode pre-5.8. We detaint is so @INC won't be tainted. This is # no worse, and probably better, than just shoving an untainted, # relative "blib/lib" onto @INC. my $cwd; BEGIN { ($cwd) = getcwd() =~ /(.*)/; } use lib map { File::Spec->rel2abs($_, $cwd) } qw(blib/arch blib/lib); 1; __END__ package ExtUtils::ParseXS; use strict; use 5.006001; use Cwd; use Config; use Exporter 'import'; use File::Basename; use File::Spec; use Symbol; our $VERSION; BEGIN { $VERSION = '3.39'; require ExtUtils::ParseXS::Constants; ExtUtils::ParseXS::Constants->VERSION($VERSION); require ExtUtils::ParseXS::CountLines; ExtUtils::ParseXS::CountLines->VERSION($VERSION); require ExtUtils::ParseXS::Utilities; ExtUtils::ParseXS::Utilities->VERSION($VERSION); require ExtUtils::ParseXS::Eval; ExtUtils::ParseXS::Eval->VERSION($VERSION); } $VERSION = eval $VERSION if $VERSION =~ /_/; use ExtUtils::ParseXS::Utilities qw( standard_typemap_locations trim_whitespace C_string valid_proto_string process_typemaps map_type standard_XS_defs assign_func_args analyze_preprocessor_statements set_cond Warn current_line_number blurt death check_conditional_preprocessor_statements escape_file_for_line_directive report_typemap_failure ); our @EXPORT_OK = qw( process_file report_error_count ); ############################## # A number of "constants" our ($C_group_rex, $C_arg); # Group in C (no support for comments or literals) $C_group_rex = qr/ [({\[] (?: (?> [^()\[\]{}]+ ) | (??{ $C_group_rex }) )* [)}\]] /x; # Chunk in C without comma at toplevel (no comments): $C_arg = qr/ (?: (?> [^()\[\]{},"']+ ) | (??{ $C_group_rex }) | " (?: (?> [^\\"]+ ) | \\. )* " # String literal | ' (?: (?> [^\\']+ ) | \\. )* ' # Char literal )* /xs; # "impossible" keyword (multiple newline) my $END = "!End!\n\n"; # Match an XS Keyword my $BLOCK_regexp = '\s*(' . $ExtUtils::ParseXS::Constants::XSKeywordsAlternation . "|$END)\\s*:"; sub new { return bless {} => shift; } our $Singleton = __PACKAGE__->new; sub process_file { my $self; # Allow for $package->process_file(%hash), $obj->process_file, and process_file() if (@_ % 2) { my $invocant = shift; $self = ref($invocant) ? $invocant : $invocant->new; } else { $self = $Singleton; } my %options = @_; $self->{ProtoUsed} = exists $options{prototypes}; # Set defaults. my %args = ( argtypes => 1, csuffix => '.c', except => 0, hiertype => 0, inout => 1, linenumbers => 1, optimize => 1, output => \*STDOUT, prototypes => 0, typemap => [], versioncheck => 1, FH => Symbol::gensym(), %options, ); $args{except} = $args{except} ? ' TRY' : ''; # Global Constants my ($Is_VMS, $SymSet); if ($^O eq 'VMS') { $Is_VMS = 1; # Establish set of global symbols with max length 28, since xsubpp # will later add the 'XS_' prefix. require ExtUtils::XSSymSet; $SymSet = ExtUtils::XSSymSet->new(28); } @{ $self->{XSStack} } = ({type => 'none'}); $self->{InitFileCode} = [ @ExtUtils::ParseXS::Constants::InitFileCode ]; $self->{Overload} = 0; # bool $self->{errors} = 0; # count $self->{Fallback} = '&PL_sv_undef'; # Most of the 1500 lines below uses these globals. We'll have to # clean this up sometime, probably. For now, we just pull them out # of %args. -Ken $self->{RetainCplusplusHierarchicalTypes} = $args{hiertype}; $self->{WantPrototypes} = $args{prototypes}; $self->{WantVersionChk} = $args{versioncheck}; $self->{WantLineNumbers} = $args{linenumbers}; $self->{IncludedFiles} = {}; die "Missing required parameter 'filename'" unless $args{filename}; $self->{filepathname} = $args{filename}; ($self->{dir}, $self->{filename}) = (dirname($args{filename}), basename($args{filename})); $self->{filepathname} =~ s/\\/\\\\/g; $self->{IncludedFiles}->{$args{filename}}++; # Open the output file if given as a string. If they provide some # other kind of reference, trust them that we can print to it. if (not ref $args{output}) { open my($fh), "> $args{output}" or die "Can't create $args{output}: $!"; $args{outfile} = $args{output}; $args{output} = $fh; } # Really, we shouldn't have to chdir() or select() in the first # place. For now, just save and restore. my $orig_cwd = cwd(); my $orig_fh = select(); chdir($self->{dir}); my $pwd = cwd(); my $csuffix = $args{csuffix}; if ($self->{WantLineNumbers}) { my $cfile; if ( $args{outfile} ) { $cfile = $args{outfile}; } else { $cfile = $args{filename}; $cfile =~ s/\.xs$/$csuffix/i or $cfile .= $csuffix; } tie(*PSEUDO_STDOUT, 'ExtUtils::ParseXS::CountLines', $cfile, $args{output}); select PSEUDO_STDOUT; } else { select $args{output}; } $self->{typemap} = process_typemaps( $args{typemap}, $pwd ); # Move more settings from parameters to object foreach my $datum ( qw| argtypes except inout optimize | ) { $self->{$datum} = $args{$datum}; } $self->{strip_c_func_prefix} = $args{s}; # Identify the version of xsubpp used print <{filename}. Do not edit this file, edit $self->{filename} instead. * * ANY CHANGES MADE HERE WILL BE LOST! * */ EOM print("#line 1 \"" . escape_file_for_line_directive($self->{filepathname}) . "\"\n") if $self->{WantLineNumbers}; # Open the input file (using $self->{filename} which # is a basename'd $args{filename} due to chdir above) open($self->{FH}, '<', $self->{filename}) or die "cannot open $self->{filename}: $!\n"; FIRSTMODULE: while (readline($self->{FH})) { if (/^=/) { my $podstartline = $.; do { if (/^=cut\s*$/) { # We can't just write out a /* */ comment, as our embedded # POD might itself be in a comment. We can't put a /**/ # comment inside #if 0, as the C standard says that the source # file is decomposed into preprocessing characters in the stage # before preprocessing commands are executed. # I don't want to leave the text as barewords, because the spec # isn't clear whether macros are expanded before or after # preprocessing commands are executed, and someone pathological # may just have defined one of the 3 words as a macro that does # something strange. Multiline strings are illegal in C, so # the "" we write must be a string literal. And they aren't # concatenated until 2 steps later, so we are safe. # - Nicholas Clark print("#if 0\n \"Skipped embedded POD.\"\n#endif\n"); printf("#line %d \"%s\"\n", $. + 1, escape_file_for_line_directive($self->{filepathname})) if $self->{WantLineNumbers}; next FIRSTMODULE; } } while (readline($self->{FH})); # At this point $. is at end of file so die won't state the start # of the problem, and as we haven't yet read any lines &death won't # show the correct line in the message either. die ("Error: Unterminated pod in $self->{filename}, line $podstartline\n") unless $self->{lastline}; } last if ($self->{Package}, $self->{Prefix}) = /^MODULE\s*=\s*[\w:]+(?:\s+PACKAGE\s*=\s*([\w:]+))?(?:\s+PREFIX\s*=\s*(\S+))?\s*$/; print $_; } unless (defined $_) { warn "Didn't find a 'MODULE ... PACKAGE ... PREFIX' line\n"; exit 0; # Not a fatal error for the caller process } print 'ExtUtils::ParseXS::CountLines'->end_marker, "\n" if $self->{WantLineNumbers}; standard_XS_defs(); print 'ExtUtils::ParseXS::CountLines'->end_marker, "\n" if $self->{WantLineNumbers}; $self->{lastline} = $_; $self->{lastline_no} = $.; my $BootCode_ref = []; my $XSS_work_idx = 0; my $cpp_next_tmp = 'XSubPPtmpAAAA'; PARAGRAPH: while ($self->fetch_para()) { my $outlist_ref = []; # Print initial preprocessor statements and blank lines while (@{ $self->{line} } && $self->{line}->[0] !~ /^[^\#]/) { my $ln = shift(@{ $self->{line} }); print $ln, "\n"; next unless $ln =~ /^\#\s*((if)(?:n?def)?|elsif|else|endif)\b/; my $statement = $+; ( $self, $XSS_work_idx, $BootCode_ref ) = analyze_preprocessor_statements( $self, $statement, $XSS_work_idx, $BootCode_ref ); } next PARAGRAPH unless @{ $self->{line} }; if ($XSS_work_idx && !$self->{XSStack}->[$XSS_work_idx]{varname}) { # We are inside an #if, but have not yet #defined its xsubpp variable. print "#define $cpp_next_tmp 1\n\n"; push(@{ $self->{InitFileCode} }, "#if $cpp_next_tmp\n"); push(@{ $BootCode_ref }, "#if $cpp_next_tmp"); $self->{XSStack}->[$XSS_work_idx]{varname} = $cpp_next_tmp++; } $self->death( "Code is not inside a function" ." (maybe last function was ended by a blank line " ." followed by a statement on column one?)") if $self->{line}->[0] =~ /^\s/; # initialize info arrays foreach my $member (qw(args_match var_types defaults arg_list argtype_seen in_out lengthof)) { $self->{$member} = {}; } $self->{proto_arg} = []; $self->{processing_arg_with_types} = 0; # bool $self->{proto_in_this_xsub} = 0; # counter & bool $self->{scope_in_this_xsub} = 0; # counter & bool $self->{interface} = 0; # bool $self->{interface_macro} = 'XSINTERFACE_FUNC'; $self->{interface_macro_set} = 'XSINTERFACE_FUNC_SET'; $self->{ProtoThisXSUB} = $self->{WantPrototypes}; # states 0 (none), 1 (yes), 2 (empty prototype) $self->{ScopeThisXSUB} = 0; # bool my $xsreturn = 0; $_ = shift(@{ $self->{line} }); while (my $kwd = $self->check_keyword("REQUIRE|PROTOTYPES|EXPORT_XSUB_SYMBOLS|FALLBACK|VERSIONCHECK|INCLUDE(?:_COMMAND)?|SCOPE")) { my $method = $kwd . "_handler"; $self->$method($_); next PARAGRAPH unless @{ $self->{line} }; $_ = shift(@{ $self->{line} }); } if ($self->check_keyword("BOOT")) { check_conditional_preprocessor_statements($self); push (@{ $BootCode_ref }, "#line $self->{line_no}->[@{ $self->{line_no} } - @{ $self->{line} }] \"" . escape_file_for_line_directive($self->{filepathname}) . "\"") if $self->{WantLineNumbers} && $self->{line}->[0] !~ /^\s*#\s*line\b/; push (@{ $BootCode_ref }, @{ $self->{line} }, ""); next PARAGRAPH; } # extract return type, function name and arguments ($self->{ret_type}) = ExtUtils::Typemaps::tidy_type($_); my $RETVAL_no_return = 1 if $self->{ret_type} =~ s/^NO_OUTPUT\s+//; # Allow one-line ANSI-like declaration unshift @{ $self->{line} }, $2 if $self->{argtypes} and $self->{ret_type} =~ s/^(.*?\w.*?)\s*\b(\w+\s*\(.*)/$1/s; # a function definition needs at least 2 lines $self->blurt("Error: Function definition too short '$self->{ret_type}'"), next PARAGRAPH unless @{ $self->{line} }; my $externC = 1 if $self->{ret_type} =~ s/^extern "C"\s+//; my $static = 1 if $self->{ret_type} =~ s/^static\s+//; my $func_header = shift(@{ $self->{line} }); $self->blurt("Error: Cannot parse function definition from '$func_header'"), next PARAGRAPH unless $func_header =~ /^(?:([\w:]*)::)?(\w+)\s*\(\s*(.*?)\s*\)\s*(const)?\s*(;\s*)?$/s; my ($class, $orig_args); ($class, $self->{func_name}, $orig_args) = ($1, $2, $3); $class = "$4 $class" if $4; ($self->{pname} = $self->{func_name}) =~ s/^($self->{Prefix})?/$self->{Packprefix}/; my $clean_func_name; ($clean_func_name = $self->{func_name}) =~ s/^$self->{Prefix}//; $self->{Full_func_name} = "$self->{Packid}_$clean_func_name"; if ($Is_VMS) { $self->{Full_func_name} = $SymSet->addsym( $self->{Full_func_name} ); } # Check for duplicate function definition for my $tmp (@{ $self->{XSStack} }) { next unless defined $tmp->{functions}{ $self->{Full_func_name} }; Warn( $self, "Warning: duplicate function definition '$clean_func_name' detected"); last; } $self->{XSStack}->[$XSS_work_idx]{functions}{ $self->{Full_func_name} }++; delete $self->{XsubAliases}; delete $self->{XsubAliasValues}; %{ $self->{Interfaces} } = (); @{ $self->{Attributes} } = (); $self->{DoSetMagic} = 1; $orig_args =~ s/\\\s*/ /g; # process line continuations my @args; my (@fake_INPUT_pre); # For length(s) generated variables my (@fake_INPUT); my $only_C_inlist_ref = {}; # Not in the signature of Perl function if ($self->{argtypes} and $orig_args =~ /\S/) { my $args = "$orig_args ,"; use re 'eval'; if ($args =~ /^( (??{ $C_arg }) , )* $ /x) { @args = ($args =~ /\G ( (??{ $C_arg }) ) , /xg); no re 'eval'; for ( @args ) { s/^\s+//; s/\s+$//; my ($arg, $default) = ($_ =~ m/ ( [^=]* ) ( (?: = .* )? ) /x); my ($pre, $len_name) = ($arg =~ /(.*?) \s* \b ( \w+ | length\( \s*\w+\s* \) ) \s* $ /x); next unless defined($pre) && length($pre); my $out_type = ''; my $inout_var; if ($self->{inout} and s/^(IN|IN_OUTLIST|OUTLIST|OUT|IN_OUT)\b\s*//) { my $type = $1; $out_type = $type if $type ne 'IN'; $arg =~ s/^(IN|IN_OUTLIST|OUTLIST|OUT|IN_OUT)\b\s*//; $pre =~ s/^(IN|IN_OUTLIST|OUTLIST|OUT|IN_OUT)\b\s*//; } my $islength; if ($len_name =~ /^length\( \s* (\w+) \s* \)\z/x) { $len_name = "XSauto_length_of_$1"; $islength = 1; die "Default value on length() argument: '$_'" if length $default; } if (length $pre or $islength) { # Has a type if ($islength) { push @fake_INPUT_pre, $arg; } else { push @fake_INPUT, $arg; } # warn "pushing '$arg'\n"; $self->{argtype_seen}->{$len_name}++; $_ = "$len_name$default"; # Assigns to @args } $only_C_inlist_ref->{$_} = 1 if $out_type eq "OUTLIST" or $islength; push @{ $outlist_ref }, $len_name if $out_type =~ /OUTLIST$/; $self->{in_out}->{$len_name} = $out_type if $out_type; } } else { no re 'eval'; @args = split(/\s*,\s*/, $orig_args); Warn( $self, "Warning: cannot parse argument list '$orig_args', fallback to split"); } } else { @args = split(/\s*,\s*/, $orig_args); for (@args) { if ($self->{inout} and s/^(IN|IN_OUTLIST|OUTLIST|IN_OUT|OUT)\b\s*//) { my $out_type = $1; next if $out_type eq 'IN'; $only_C_inlist_ref->{$_} = 1 if $out_type eq "OUTLIST"; if ($out_type =~ /OUTLIST$/) { push @{ $outlist_ref }, undef; } $self->{in_out}->{$_} = $out_type; } } } if (defined($class)) { my $arg0 = ((defined($static) or $self->{func_name} eq 'new') ? "CLASS" : "THIS"); unshift(@args, $arg0); } my $extra_args = 0; my @args_num = (); my $num_args = 0; my $report_args = ''; my $ellipsis; foreach my $i (0 .. $#args) { if ($args[$i] =~ s/\.\.\.//) { $ellipsis = 1; if ($args[$i] eq '' && $i == $#args) { $report_args .= ", ..."; pop(@args); last; } } if ($only_C_inlist_ref->{$args[$i]}) { push @args_num, undef; } else { push @args_num, ++$num_args; $report_args .= ", $args[$i]"; } if ($args[$i] =~ /^([^=]*[^\s=])\s*=\s*(.*)/s) { $extra_args++; $args[$i] = $1; $self->{defaults}->{$args[$i]} = $2; $self->{defaults}->{$args[$i]} =~ s/"/\\"/g; } $self->{proto_arg}->[$i+1] = '$'; } my $min_args = $num_args - $extra_args; $report_args =~ s/"/\\"/g; $report_args =~ s/^,\s+//; $self->{func_args} = assign_func_args($self, \@args, $class); @{ $self->{args_match} }{@args} = @args_num; my $PPCODE = grep(/^\s*PPCODE\s*:/, @{ $self->{line} }); my $CODE = grep(/^\s*CODE\s*:/, @{ $self->{line} }); # Detect CODE: blocks which use ST(n)= or XST_m*(n,v) # to set explicit return values. my $EXPLICIT_RETURN = ($CODE && ("@{ $self->{line} }" =~ /(\bST\s*\([^;]*=) | (\bXST_m\w+\s*\()/x )); $self->{ALIAS} = grep(/^\s*ALIAS\s*:/, @{ $self->{line} }); my $INTERFACE = grep(/^\s*INTERFACE\s*:/, @{ $self->{line} }); $xsreturn = 1 if $EXPLICIT_RETURN; $externC = $externC ? qq[extern "C"] : ""; # print function header print Q(<<"EOF"); #$externC #XS_EUPXS(XS_$self->{Full_func_name}); /* prototype to pass -Wmissing-prototypes */ #XS_EUPXS(XS_$self->{Full_func_name}) #[[ # dVAR; dXSARGS; EOF print Q(<<"EOF") if $self->{ALIAS}; # dXSI32; EOF print Q(<<"EOF") if $INTERFACE; # dXSFUNCTION($self->{ret_type}); EOF $self->{cond} = set_cond($ellipsis, $min_args, $num_args); print Q(<<"EOF") if $self->{except}; # char errbuf[1024]; # *errbuf = '\\0'; EOF if($self->{cond}) { print Q(<<"EOF"); # if ($self->{cond}) # croak_xs_usage(cv, "$report_args"); EOF } else { # cv and items likely to be unused print Q(<<"EOF"); # PERL_UNUSED_VAR(cv); /* -W */ # PERL_UNUSED_VAR(items); /* -W */ EOF } #gcc -Wall: if an xsub has PPCODE is used #it is possible none of ST, XSRETURN or XSprePUSH macros are used #hence 'ax' (setup by dXSARGS) is unused #XXX: could breakup the dXSARGS; into dSP;dMARK;dITEMS #but such a move could break third-party extensions print Q(<<"EOF") if $PPCODE; # PERL_UNUSED_VAR(ax); /* -Wall */ EOF print Q(<<"EOF") if $PPCODE; # SP -= items; EOF # Now do a block of some sort. $self->{condnum} = 0; $self->{cond} = ''; # last CASE: conditional push(@{ $self->{line} }, "$END:"); push(@{ $self->{line_no} }, $self->{line_no}->[-1]); $_ = ''; check_conditional_preprocessor_statements(); while (@{ $self->{line} }) { $self->CASE_handler($_) if $self->check_keyword("CASE"); print Q(<<"EOF"); # $self->{except} [[ EOF # do initialization of input variables $self->{thisdone} = 0; $self->{retvaldone} = 0; $self->{deferred} = ""; %{ $self->{arg_list} } = (); $self->{gotRETVAL} = 0; $self->INPUT_handler($_); $self->process_keyword("INPUT|PREINIT|INTERFACE_MACRO|C_ARGS|ALIAS|ATTRS|PROTOTYPE|SCOPE|OVERLOAD"); print Q(<<"EOF") if $self->{ScopeThisXSUB}; # ENTER; # [[ EOF if (!$self->{thisdone} && defined($class)) { if (defined($static) or $self->{func_name} eq 'new') { print "\tchar *"; $self->{var_types}->{"CLASS"} = "char *"; $self->generate_init( { type => "char *", num => 1, var => "CLASS", printed_name => undef, } ); } else { print "\t" . map_type($self, "$class *"); $self->{var_types}->{"THIS"} = "$class *"; $self->generate_init( { type => "$class *", num => 1, var => "THIS", printed_name => undef, } ); } } # These are set if OUTPUT is found and/or CODE using RETVAL $self->{have_OUTPUT} = $self->{have_CODE_with_RETVAL} = 0; my ($wantRETVAL); # do code if (/^\s*NOT_IMPLEMENTED_YET/) { print "\n\tPerl_croak(aTHX_ \"$self->{pname}: not implemented yet\");\n"; $_ = ''; } else { if ($self->{ret_type} ne "void") { print "\t" . map_type($self, $self->{ret_type}, 'RETVAL') . ";\n" if !$self->{retvaldone}; $self->{args_match}->{"RETVAL"} = 0; $self->{var_types}->{"RETVAL"} = $self->{ret_type}; my $outputmap = $self->{typemap}->get_outputmap( ctype => $self->{ret_type} ); print "\tdXSTARG;\n" if $self->{optimize} and $outputmap and $outputmap->targetable; } if (@fake_INPUT or @fake_INPUT_pre) { unshift @{ $self->{line} }, @fake_INPUT_pre, @fake_INPUT, $_; $_ = ""; $self->{processing_arg_with_types} = 1; $self->INPUT_handler($_); } print $self->{deferred}; $self->process_keyword("INIT|ALIAS|ATTRS|PROTOTYPE|INTERFACE_MACRO|INTERFACE|C_ARGS|OVERLOAD"); if ($self->check_keyword("PPCODE")) { $self->print_section(); $self->death("PPCODE must be last thing") if @{ $self->{line} }; print "\tLEAVE;\n" if $self->{ScopeThisXSUB}; print "\tPUTBACK;\n\treturn;\n"; } elsif ($self->check_keyword("CODE")) { my $consumed_code = $self->print_section(); if ($consumed_code =~ /\bRETVAL\b/) { $self->{have_CODE_with_RETVAL} = 1; } } elsif (defined($class) and $self->{func_name} eq "DESTROY") { print "\n\t"; print "delete THIS;\n"; } else { print "\n\t"; if ($self->{ret_type} ne "void") { print "RETVAL = "; $wantRETVAL = 1; } if (defined($static)) { if ($self->{func_name} eq 'new') { $self->{func_name} = "$class"; } else { print "${class}::"; } } elsif (defined($class)) { if ($self->{func_name} eq 'new') { $self->{func_name} .= " $class"; } else { print "THIS->"; } } my $strip = $self->{strip_c_func_prefix}; $self->{func_name} =~ s/^\Q$strip// if defined $strip; $self->{func_name} = 'XSFUNCTION' if $self->{interface}; print "$self->{func_name}($self->{func_args});\n"; } } # do output variables $self->{gotRETVAL} = 0; # 1 if RETVAL seen in OUTPUT section; undef $self->{RETVAL_code} ; # code to set RETVAL (from OUTPUT section); # $wantRETVAL set if 'RETVAL =' autogenerated ($wantRETVAL, $self->{ret_type}) = (0, 'void') if $RETVAL_no_return; undef %{ $self->{outargs} }; $self->process_keyword("POSTCALL|OUTPUT|ALIAS|ATTRS|PROTOTYPE|OVERLOAD"); # A CODE section with RETVAL, but no OUTPUT? FAIL! if ($self->{have_CODE_with_RETVAL} and not $self->{have_OUTPUT} and $self->{ret_type} ne 'void') { $self->Warn("Warning: Found a 'CODE' section which seems to be using 'RETVAL' but no 'OUTPUT' section."); } $self->generate_output( { type => $self->{var_types}->{$_}, num => $self->{args_match}->{$_}, var => $_, do_setmagic => $self->{DoSetMagic}, do_push => undef, } ) for grep $self->{in_out}->{$_} =~ /OUT$/, sort keys %{ $self->{in_out} }; my $prepush_done; # all OUTPUT done, so now push the return value on the stack if ($self->{gotRETVAL} && $self->{RETVAL_code}) { print "\t$self->{RETVAL_code}\n"; } elsif ($self->{gotRETVAL} || $wantRETVAL) { my $outputmap = $self->{typemap}->get_outputmap( ctype => $self->{ret_type} ); my $trgt = $self->{optimize} && $outputmap && $outputmap->targetable; my $var = 'RETVAL'; my $type = $self->{ret_type}; if ($trgt) { my $what = $self->eval_output_typemap_code( qq("$trgt->{what}"), {var => $var, type => $self->{ret_type}} ); if (not $trgt->{with_size} and $trgt->{type} eq 'p') { # sv_setpv # PUSHp corresponds to sv_setpvn. Treat sv_setpv directly print "\tsv_setpv(TARG, $what); XSprePUSH; PUSHTARG;\n"; $prepush_done = 1; } else { my $tsize = $trgt->{what_size}; $tsize = '' unless defined $tsize; $tsize = $self->eval_output_typemap_code( qq("$tsize"), {var => $var, type => $self->{ret_type}} ); print "\tXSprePUSH; PUSH$trgt->{type}($what$tsize);\n"; $prepush_done = 1; } } else { # RETVAL almost never needs SvSETMAGIC() $self->generate_output( { type => $self->{ret_type}, num => 0, var => 'RETVAL', do_setmagic => 0, do_push => undef, } ); } } $xsreturn = 1 if $self->{ret_type} ne "void"; my $num = $xsreturn; my $c = @{ $outlist_ref }; print "\tXSprePUSH;" if $c and not $prepush_done; print "\tEXTEND(SP,$c);\n" if $c; $xsreturn += $c; $self->generate_output( { type => $self->{var_types}->{$_}, num => $num++, var => $_, do_setmagic => 0, do_push => 1, } ) for @{ $outlist_ref }; # do cleanup $self->process_keyword("CLEANUP|ALIAS|ATTRS|PROTOTYPE|OVERLOAD"); print Q(<<"EOF") if $self->{ScopeThisXSUB}; # ]] EOF print Q(<<"EOF") if $self->{ScopeThisXSUB} and not $PPCODE; # LEAVE; EOF # print function trailer print Q(<<"EOF"); # ]] EOF print Q(<<"EOF") if $self->{except}; # BEGHANDLERS # CATCHALL # sprintf(errbuf, "%s: %s\\tpropagated", Xname, Xreason); # ENDHANDLERS EOF if ($self->check_keyword("CASE")) { $self->blurt("Error: No 'CASE:' at top of function") unless $self->{condnum}; $_ = "CASE: $_"; # Restore CASE: label next; } last if $_ eq "$END:"; $self->death(/^$BLOCK_regexp/o ? "Misplaced '$1:'" : "Junk at end of function ($_)"); } print Q(<<"EOF") if $self->{except}; # if (errbuf[0]) # Perl_croak(aTHX_ errbuf); EOF if ($xsreturn) { print Q(<<"EOF") unless $PPCODE; # XSRETURN($xsreturn); EOF } else { print Q(<<"EOF") unless $PPCODE; # XSRETURN_EMPTY; EOF } print Q(<<"EOF"); #]] # EOF $self->{proto} = ""; unless($self->{ProtoThisXSUB}) { $self->{newXS} = "newXS_deffile"; $self->{file} = ""; } else { # Build the prototype string for the xsub $self->{newXS} = "newXSproto_portable"; $self->{file} = ", file"; if ($self->{ProtoThisXSUB} eq 2) { # User has specified empty prototype } elsif ($self->{ProtoThisXSUB} eq 1) { my $s = ';'; if ($min_args < $num_args) { $s = ''; $self->{proto_arg}->[$min_args] .= ";"; } push @{ $self->{proto_arg} }, "$s\@" if $ellipsis; $self->{proto} = join ("", grep defined, @{ $self->{proto_arg} } ); } else { # User has specified a prototype $self->{proto} = $self->{ProtoThisXSUB}; } $self->{proto} = qq{, "$self->{proto}"}; } if ($self->{XsubAliases} and keys %{ $self->{XsubAliases} }) { $self->{XsubAliases}->{ $self->{pname} } = 0 unless defined $self->{XsubAliases}->{ $self->{pname} }; foreach my $xname (sort keys %{ $self->{XsubAliases} }) { my $value = $self->{XsubAliases}{$xname}; push(@{ $self->{InitFileCode} }, Q(<<"EOF")); # cv = $self->{newXS}(\"$xname\", XS_$self->{Full_func_name}$self->{file}$self->{proto}); # XSANY.any_i32 = $value; EOF } } elsif (@{ $self->{Attributes} }) { push(@{ $self->{InitFileCode} }, Q(<<"EOF")); # cv = $self->{newXS}(\"$self->{pname}\", XS_$self->{Full_func_name}$self->{file}$self->{proto}); # apply_attrs_string("$self->{Package}", cv, "@{ $self->{Attributes} }", 0); EOF } elsif ($self->{interface}) { foreach my $yname (sort keys %{ $self->{Interfaces} }) { my $value = $self->{Interfaces}{$yname}; $yname = "$self->{Package}\::$yname" unless $yname =~ /::/; push(@{ $self->{InitFileCode} }, Q(<<"EOF")); # cv = $self->{newXS}(\"$yname\", XS_$self->{Full_func_name}$self->{file}$self->{proto}); # $self->{interface_macro_set}(cv,$value); EOF } } elsif($self->{newXS} eq 'newXS_deffile'){ # work around P5NCI's empty newXS macro push(@{ $self->{InitFileCode} }, " $self->{newXS}(\"$self->{pname}\", XS_$self->{Full_func_name}$self->{file}$self->{proto});\n"); } else { push(@{ $self->{InitFileCode} }, " (void)$self->{newXS}(\"$self->{pname}\", XS_$self->{Full_func_name}$self->{file}$self->{proto});\n"); } } # END 'PARAGRAPH' 'while' loop if ($self->{Overload}) { # make it findable with fetchmethod print Q(<<"EOF"); #XS_EUPXS(XS_$self->{Packid}_nil); /* prototype to pass -Wmissing-prototypes */ #XS_EUPXS(XS_$self->{Packid}_nil) #{ # dXSARGS; # PERL_UNUSED_VAR(items); # XSRETURN_EMPTY; #} # EOF unshift(@{ $self->{InitFileCode} }, <<"MAKE_FETCHMETHOD_WORK"); /* Making a sub named "$self->{Package}::()" allows the package */ /* to be findable via fetchmethod(), and causes */ /* overload::Overloaded("$self->{Package}") to return true. */ (void)$self->{newXS}("$self->{Package}::()", XS_$self->{Packid}_nil$self->{file}$self->{proto}); MAKE_FETCHMETHOD_WORK } # print initialization routine print Q(<<"EOF"); ##ifdef __cplusplus #extern "C" ##endif EOF print Q(<<"EOF"); #XS_EXTERNAL(boot_$self->{Module_cname}); /* prototype to pass -Wmissing-prototypes */ #XS_EXTERNAL(boot_$self->{Module_cname}) #[[ ##if PERL_VERSION_LE(5, 21, 5) # dVAR; dXSARGS; ##else # dVAR; ${\($self->{WantVersionChk} ? 'dXSBOOTARGSXSAPIVERCHK;' : 'dXSBOOTARGSAPIVERCHK;')} ##endif EOF #Under 5.8.x and lower, newXS is declared in proto.h as expecting a non-const #file name argument. If the wrong qualifier is used, it causes breakage with #C++ compilers and warnings with recent gcc. #-Wall: if there is no $self->{Full_func_name} there are no xsubs in this .xs #so 'file' is unused print Q(<<"EOF") if $self->{Full_func_name}; ##if (PERL_REVISION == 5 && PERL_VERSION < 9) # char* file = __FILE__; ##else # const char* file = __FILE__; ##endif # # PERL_UNUSED_VAR(file); EOF print Q("#\n"); print Q(<<"EOF"); # PERL_UNUSED_VAR(cv); /* -W */ # PERL_UNUSED_VAR(items); /* -W */ EOF if( $self->{WantVersionChk}){ print Q(<<"EOF") ; ##if PERL_VERSION_LE(5, 21, 5) # XS_VERSION_BOOTCHECK; ## ifdef XS_APIVERSION_BOOTCHECK # XS_APIVERSION_BOOTCHECK; ## endif ##endif EOF } else { print Q(<<"EOF") ; ##if PERL_VERSION_LE(5, 21, 5) && defined(XS_APIVERSION_BOOTCHECK) # XS_APIVERSION_BOOTCHECK; ##endif EOF } print Q(<<"EOF") if defined $self->{XsubAliases} or defined $self->{interfaces}; # { # CV * cv; # EOF print Q(<<"EOF") if ($self->{Overload}); # /* register the overloading (type 'A') magic */ ##if (PERL_REVISION == 5 && PERL_VERSION < 9) # PL_amagic_generation++; ##endif # /* The magic for overload gets a GV* via gv_fetchmeth as */ # /* mentioned above, and looks in the SV* slot of it for */ # /* the "fallback" status. */ # sv_setsv( # get_sv( "$self->{Package}::()", TRUE ), # $self->{Fallback} # ); EOF print @{ $self->{InitFileCode} }; print Q(<<"EOF") if defined $self->{XsubAliases} or defined $self->{interfaces}; # } EOF if (@{ $BootCode_ref }) { print "\n /* Initialisation Section */\n\n"; @{ $self->{line} } = @{ $BootCode_ref }; $self->print_section(); print "\n /* End of Initialisation Section */\n\n"; } print Q(<<'EOF'); ##if PERL_VERSION_LE(5, 21, 5) ## if PERL_VERSION_GE(5, 9, 0) # if (PL_unitcheckav) # call_list(PL_scopestack_ix, PL_unitcheckav); ## endif # XSRETURN_YES; ##else # Perl_xs_boot_epilog(aTHX_ ax); ##endif #]] # EOF warn("Please specify prototyping behavior for $self->{filename} (see perlxs manual)\n") unless $self->{ProtoUsed}; chdir($orig_cwd); select($orig_fh); untie *PSEUDO_STDOUT if tied *PSEUDO_STDOUT; close $self->{FH}; return 1; } sub report_error_count { if (@_) { return $_[0]->{errors}||0; } else { return $Singleton->{errors}||0; } } # Input: ($self, $_, @{ $self->{line} }) == unparsed input. # Output: ($_, @{ $self->{line} }) == (rest of line, following lines). # Return: the matched keyword if found, otherwise 0 sub check_keyword { my $self = shift; $_ = shift(@{ $self->{line} }) while !/\S/ && @{ $self->{line} }; s/^(\s*)($_[0])\s*:\s*(?:#.*)?/$1/s && $2; } sub print_section { my $self = shift; # the "do" is required for right semantics do { $_ = shift(@{ $self->{line} }) } while !/\S/ && @{ $self->{line} }; my $consumed_code = ''; print("#line ", $self->{line_no}->[@{ $self->{line_no} } - @{ $self->{line} } -1], " \"", escape_file_for_line_directive($self->{filepathname}), "\"\n") if $self->{WantLineNumbers} && !/^\s*#\s*line\b/ && !/^#if XSubPPtmp/; for (; defined($_) && !/^$BLOCK_regexp/o; $_ = shift(@{ $self->{line} })) { print "$_\n"; $consumed_code .= "$_\n"; } print 'ExtUtils::ParseXS::CountLines'->end_marker, "\n" if $self->{WantLineNumbers}; return $consumed_code; } sub merge_section { my $self = shift; my $in = ''; while (!/\S/ && @{ $self->{line} }) { $_ = shift(@{ $self->{line} }); } for (; defined($_) && !/^$BLOCK_regexp/o; $_ = shift(@{ $self->{line} })) { $in .= "$_\n"; } chomp $in; return $in; } sub process_keyword { my($self, $pattern) = @_; while (my $kwd = $self->check_keyword($pattern)) { my $method = $kwd . "_handler"; $self->$method($_); } } sub CASE_handler { my $self = shift; $_ = shift; $self->blurt("Error: 'CASE:' after unconditional 'CASE:'") if $self->{condnum} && $self->{cond} eq ''; $self->{cond} = $_; trim_whitespace($self->{cond}); print " ", ($self->{condnum}++ ? " else" : ""), ($self->{cond} ? " if ($self->{cond})\n" : "\n"); $_ = ''; } sub INPUT_handler { my $self = shift; $_ = shift; for (; !/^$BLOCK_regexp/o; $_ = shift(@{ $self->{line} })) { last if /^\s*NOT_IMPLEMENTED_YET/; next unless /\S/; # skip blank lines trim_whitespace($_); my $ln = $_; # remove trailing semicolon if no initialisation s/\s*;$//g unless /[=;+].*\S/; # Process the length(foo) declarations if (s/^([^=]*)\blength\(\s*(\w+)\s*\)\s*$/$1 XSauto_length_of_$2=NO_INIT/x) { print "\tSTRLEN\tSTRLEN_length_of_$2;\n"; $self->{lengthof}->{$2} = undef; $self->{deferred} .= "\n\tXSauto_length_of_$2 = STRLEN_length_of_$2;\n"; } # check for optional initialisation code my $var_init = ''; $var_init = $1 if s/\s*([=;+].*)$//s; $var_init =~ s/"/\\"/g; # *sigh* It's valid to supply explicit input typemaps in the argument list... my $is_overridden_typemap = $var_init =~ /ST\s*\(|\$arg\b/; s/\s+/ /g; my ($var_type, $var_addr, $var_name) = /^(.*?[^&\s])\s*(\&?)\s*\b(\w+)$/s or $self->blurt("Error: invalid argument declaration '$ln'"), next; # Check for duplicate definitions $self->blurt("Error: duplicate definition of argument '$var_name' ignored"), next if $self->{arg_list}->{$var_name}++ or defined $self->{argtype_seen}->{$var_name} and not $self->{processing_arg_with_types}; $self->{thisdone} |= $var_name eq "THIS"; $self->{retvaldone} |= $var_name eq "RETVAL"; $self->{var_types}->{$var_name} = $var_type; # XXXX This check is a safeguard against the unfinished conversion of # generate_init(). When generate_init() is fixed, # one can use 2-args map_type() unconditionally. my $printed_name; if ($var_type =~ / \( \s* \* \s* \) /x) { # Function pointers are not yet supported with output_init()! print "\t" . map_type($self, $var_type, $var_name); $printed_name = 1; } else { print "\t" . map_type($self, $var_type, undef); $printed_name = 0; } $self->{var_num} = $self->{args_match}->{$var_name}; if ($self->{var_num}) { my $typemap = $self->{typemap}->get_typemap(ctype => $var_type); $self->report_typemap_failure($self->{typemap}, $var_type, "death") if not $typemap and not $is_overridden_typemap; $self->{proto_arg}->[$self->{var_num}] = ($typemap && $typemap->proto) || "\$"; } $self->{func_args} =~ s/\b($var_name)\b/&$1/ if $var_addr; if ($var_init =~ /^[=;]\s*NO_INIT\s*;?\s*$/ or $self->{in_out}->{$var_name} and $self->{in_out}->{$var_name} =~ /^OUT/ and $var_init !~ /\S/) { if ($printed_name) { print ";\n"; } else { print "\t$var_name;\n"; } } elsif ($var_init =~ /\S/) { $self->output_init( { type => $var_type, num => $self->{var_num}, var => $var_name, init => $var_init, printed_name => $printed_name, } ); } elsif ($self->{var_num}) { $self->generate_init( { type => $var_type, num => $self->{var_num}, var => $var_name, printed_name => $printed_name, } ); } else { print ";\n"; } } } sub OUTPUT_handler { my $self = shift; $self->{have_OUTPUT} = 1; $_ = shift; for (; !/^$BLOCK_regexp/o; $_ = shift(@{ $self->{line} })) { next unless /\S/; if (/^\s*SETMAGIC\s*:\s*(ENABLE|DISABLE)\s*/) { $self->{DoSetMagic} = ($1 eq "ENABLE" ? 1 : 0); next; } my ($outarg, $outcode) = /^\s*(\S+)\s*(.*?)\s*$/s; $self->blurt("Error: duplicate OUTPUT argument '$outarg' ignored"), next if $self->{outargs}->{$outarg}++; if (!$self->{gotRETVAL} and $outarg eq 'RETVAL') { # deal with RETVAL last $self->{RETVAL_code} = $outcode; $self->{gotRETVAL} = 1; next; } $self->blurt("Error: OUTPUT $outarg not an argument"), next unless defined($self->{args_match}->{$outarg}); $self->blurt("Error: No input definition for OUTPUT argument '$outarg' - ignored"), next unless defined $self->{var_types}->{$outarg}; $self->{var_num} = $self->{args_match}->{$outarg}; if ($outcode) { print "\t$outcode\n"; print "\tSvSETMAGIC(ST(" , $self->{var_num} - 1 , "));\n" if $self->{DoSetMagic}; } else { $self->generate_output( { type => $self->{var_types}->{$outarg}, num => $self->{var_num}, var => $outarg, do_setmagic => $self->{DoSetMagic}, do_push => undef, } ); } delete $self->{in_out}->{$outarg} # No need to auto-OUTPUT if exists $self->{in_out}->{$outarg} and $self->{in_out}->{$outarg} =~ /OUT$/; } } sub C_ARGS_handler { my $self = shift; $_ = shift; my $in = $self->merge_section(); trim_whitespace($in); $self->{func_args} = $in; } sub INTERFACE_MACRO_handler { my $self = shift; $_ = shift; my $in = $self->merge_section(); trim_whitespace($in); if ($in =~ /\s/) { # two ($self->{interface_macro}, $self->{interface_macro_set}) = split ' ', $in; } else { $self->{interface_macro} = $in; $self->{interface_macro_set} = 'UNKNOWN_CVT'; # catch later } $self->{interface} = 1; # local $self->{interfaces} = 1; # global } sub INTERFACE_handler { my $self = shift; $_ = shift; my $in = $self->merge_section(); trim_whitespace($in); foreach (split /[\s,]+/, $in) { my $iface_name = $_; $iface_name =~ s/^$self->{Prefix}//; $self->{Interfaces}->{$iface_name} = $_; } print Q(<<"EOF"); # XSFUNCTION = $self->{interface_macro}($self->{ret_type},cv,XSANY.any_dptr); EOF $self->{interface} = 1; # local $self->{interfaces} = 1; # global } sub CLEANUP_handler { my $self = shift; $self->print_section(); } sub PREINIT_handler { my $self = shift; $self->print_section(); } sub POSTCALL_handler { my $self = shift; $self->print_section(); } sub INIT_handler { my $self = shift; $self->print_section(); } sub get_aliases { my $self = shift; my ($line) = @_; my ($orig) = $line; # Parse alias definitions # format is # alias = value alias = value ... while ($line =~ s/^\s*([\w:]+)\s*=\s*(\w+)\s*//) { my ($alias, $value) = ($1, $2); my $orig_alias = $alias; # check for optional package definition in the alias $alias = $self->{Packprefix} . $alias if $alias !~ /::/; # check for duplicate alias name & duplicate value Warn( $self, "Warning: Ignoring duplicate alias '$orig_alias'") if defined $self->{XsubAliases}->{$alias}; Warn( $self, "Warning: Aliases '$orig_alias' and '$self->{XsubAliasValues}->{$value}' have identical values") if $self->{XsubAliasValues}->{$value}; $self->{XsubAliases}->{$alias} = $value; $self->{XsubAliasValues}->{$value} = $orig_alias; } blurt( $self, "Error: Cannot parse ALIAS definitions from '$orig'") if $line; } sub ATTRS_handler { my $self = shift; $_ = shift; for (; !/^$BLOCK_regexp/o; $_ = shift(@{ $self->{line} })) { next unless /\S/; trim_whitespace($_); push @{ $self->{Attributes} }, $_; } } sub ALIAS_handler { my $self = shift; $_ = shift; for (; !/^$BLOCK_regexp/o; $_ = shift(@{ $self->{line} })) { next unless /\S/; trim_whitespace($_); $self->get_aliases($_) if $_; } } sub OVERLOAD_handler { my $self = shift; $_ = shift; for (; !/^$BLOCK_regexp/o; $_ = shift(@{ $self->{line} })) { next unless /\S/; trim_whitespace($_); while ( s/^\s*([\w:"\\)\+\-\*\/\%\<\>\.\&\|\^\!\~\{\}\=]+)\s*//) { $self->{Overload} = 1 unless $self->{Overload}; my $overload = "$self->{Package}\::(".$1; push(@{ $self->{InitFileCode} }, " (void)$self->{newXS}(\"$overload\", XS_$self->{Full_func_name}$self->{file}$self->{proto});\n"); } } } sub FALLBACK_handler { my ($self, $setting) = @_; # the rest of the current line should contain either TRUE, # FALSE or UNDEF trim_whitespace($setting); $setting = uc($setting); my %map = ( TRUE => "&PL_sv_yes", 1 => "&PL_sv_yes", FALSE => "&PL_sv_no", 0 => "&PL_sv_no", UNDEF => "&PL_sv_undef", ); # check for valid FALLBACK value $self->death("Error: FALLBACK: TRUE/FALSE/UNDEF") unless exists $map{$setting}; $self->{Fallback} = $map{$setting}; } sub REQUIRE_handler { # the rest of the current line should contain a version number my ($self, $ver) = @_; trim_whitespace($ver); $self->death("Error: REQUIRE expects a version number") unless $ver; # check that the version number is of the form n.n $self->death("Error: REQUIRE: expected a number, got '$ver'") unless $ver =~ /^\d+(\.\d*)?/; $self->death("Error: xsubpp $ver (or better) required--this is only $VERSION.") unless $VERSION >= $ver; } sub VERSIONCHECK_handler { # the rest of the current line should contain either ENABLE or # DISABLE my ($self, $setting) = @_; trim_whitespace($setting); # check for ENABLE/DISABLE $self->death("Error: VERSIONCHECK: ENABLE/DISABLE") unless $setting =~ /^(ENABLE|DISABLE)/i; $self->{WantVersionChk} = 1 if $1 eq 'ENABLE'; $self->{WantVersionChk} = 0 if $1 eq 'DISABLE'; } sub PROTOTYPE_handler { my $self = shift; $_ = shift; my $specified; $self->death("Error: Only 1 PROTOTYPE definition allowed per xsub") if $self->{proto_in_this_xsub}++; for (; !/^$BLOCK_regexp/o; $_ = shift(@{ $self->{line} })) { next unless /\S/; $specified = 1; trim_whitespace($_); if ($_ eq 'DISABLE') { $self->{ProtoThisXSUB} = 0; } elsif ($_ eq 'ENABLE') { $self->{ProtoThisXSUB} = 1; } else { # remove any whitespace s/\s+//g; $self->death("Error: Invalid prototype '$_'") unless valid_proto_string($_); $self->{ProtoThisXSUB} = C_string($_); } } # If no prototype specified, then assume empty prototype "" $self->{ProtoThisXSUB} = 2 unless $specified; $self->{ProtoUsed} = 1; } sub SCOPE_handler { # Rest of line should be either ENABLE or DISABLE my ($self, $setting) = @_; $self->death("Error: Only 1 SCOPE declaration allowed per xsub") if $self->{scope_in_this_xsub}++; trim_whitespace($setting); $self->death("Error: SCOPE: ENABLE/DISABLE") unless $setting =~ /^(ENABLE|DISABLE)\b/i; $self->{ScopeThisXSUB} = ( uc($1) eq 'ENABLE' ); } sub PROTOTYPES_handler { # the rest of the current line should contain either ENABLE or # DISABLE my ($self, $setting) = @_; trim_whitespace($setting); # check for ENABLE/DISABLE $self->death("Error: PROTOTYPES: ENABLE/DISABLE") unless $setting =~ /^(ENABLE|DISABLE)/i; $self->{WantPrototypes} = 1 if $1 eq 'ENABLE'; $self->{WantPrototypes} = 0 if $1 eq 'DISABLE'; $self->{ProtoUsed} = 1; } sub EXPORT_XSUB_SYMBOLS_handler { # the rest of the current line should contain either ENABLE or # DISABLE my ($self, $setting) = @_; trim_whitespace($setting); # check for ENABLE/DISABLE $self->death("Error: EXPORT_XSUB_SYMBOLS: ENABLE/DISABLE") unless $setting =~ /^(ENABLE|DISABLE)/i; my $xs_impl = $1 eq 'ENABLE' ? 'XS_EXTERNAL' : 'XS_INTERNAL'; print Q(<<"EOF"); ##undef XS_EUPXS ##if defined(PERL_EUPXS_ALWAYS_EXPORT) ## define XS_EUPXS(name) XS_EXTERNAL(name) ##elif defined(PERL_EUPXS_NEVER_EXPORT) ## define XS_EUPXS(name) XS_INTERNAL(name) ##else ## define XS_EUPXS(name) $xs_impl(name) ##endif EOF } sub PushXSStack { my $self = shift; my %args = @_; # Save the current file context. push(@{ $self->{XSStack} }, { type => 'file', LastLine => $self->{lastline}, LastLineNo => $self->{lastline_no}, Line => $self->{line}, LineNo => $self->{line_no}, Filename => $self->{filename}, Filepathname => $self->{filepathname}, Handle => $self->{FH}, IsPipe => scalar($self->{filename} =~ /\|\s*$/), %args, }); } sub INCLUDE_handler { my $self = shift; $_ = shift; # the rest of the current line should contain a valid filename trim_whitespace($_); $self->death("INCLUDE: filename missing") unless $_; $self->death("INCLUDE: output pipe is illegal") if /^\s*\|/; # simple minded recursion detector $self->death("INCLUDE loop detected") if $self->{IncludedFiles}->{$_}; ++$self->{IncludedFiles}->{$_} unless /\|\s*$/; if (/\|\s*$/ && /^\s*perl\s/) { Warn( $self, "The INCLUDE directive with a command is discouraged." . " Use INCLUDE_COMMAND instead! In particular using 'perl'" . " in an 'INCLUDE: ... |' directive is not guaranteed to pick" . " up the correct perl. The INCLUDE_COMMAND directive allows" . " the use of \$^X as the currently running perl, see" . " 'perldoc perlxs' for details."); } $self->PushXSStack(); $self->{FH} = Symbol::gensym(); # open the new file open($self->{FH}, $_) or $self->death("Cannot open '$_': $!"); print Q(<<"EOF"); # #/* INCLUDE: Including '$_' from '$self->{filename}' */ # EOF $self->{filename} = $_; $self->{filepathname} = ( $^O =~ /^mswin/i ) ? qq($self->{dir}/$self->{filename}) # See CPAN RT #61908: gcc doesn't like backslashes on win32? : File::Spec->catfile($self->{dir}, $self->{filename}); # Prime the pump by reading the first # non-blank line # skip leading blank lines while (readline($self->{FH})) { last unless /^\s*$/; } $self->{lastline} = $_; $self->{lastline_no} = $.; } sub QuoteArgs { my $cmd = shift; my @args = split /\s+/, $cmd; $cmd = shift @args; for (@args) { $_ = q(").$_.q(") if !/^\"/ && length($_) > 0; } return join (' ', ($cmd, @args)); } # code copied from CPAN::HandleConfig::safe_quote # - that has doc saying leave if start/finish with same quote, but no code # given text, will conditionally quote it to protect from shell { my ($quote, $use_quote) = $^O eq 'MSWin32' ? (q{"}, q{"}) : (q{"'}, q{'}); sub _safe_quote { my ($self, $command) = @_; # Set up quote/default quote if (defined($command) and $command =~ /\s/ and $command !~ /[$quote]/) { return qq{$use_quote$command$use_quote} } return $command; } } sub INCLUDE_COMMAND_handler { my $self = shift; $_ = shift; # the rest of the current line should contain a valid command trim_whitespace($_); $_ = QuoteArgs($_) if $^O eq 'VMS'; $self->death("INCLUDE_COMMAND: command missing") unless $_; $self->death("INCLUDE_COMMAND: pipes are illegal") if /^\s*\|/ or /\|\s*$/; $self->PushXSStack( IsPipe => 1 ); $self->{FH} = Symbol::gensym(); # If $^X is used in INCLUDE_COMMAND, we know it's supposed to be # the same perl interpreter as we're currently running my $X = $self->_safe_quote($^X); # quotes if has spaces s/^\s*\$\^X/$X/; # open the new file open ($self->{FH}, "-|", $_) or $self->death( $self, "Cannot run command '$_' to include its output: $!"); print Q(<<"EOF"); # #/* INCLUDE_COMMAND: Including output of '$_' from '$self->{filename}' */ # EOF $self->{filename} = $_; $self->{filepathname} = $self->{filename}; #$self->{filepathname} =~ s/\"/\\"/g; # Fails? See CPAN RT #53938: MinGW Broken after 2.21 $self->{filepathname} =~ s/\\/\\\\/g; # Works according to reporter of #53938 # Prime the pump by reading the first # non-blank line # skip leading blank lines while (readline($self->{FH})) { last unless /^\s*$/; } $self->{lastline} = $_; $self->{lastline_no} = $.; } sub PopFile { my $self = shift; return 0 unless $self->{XSStack}->[-1]{type} eq 'file'; my $data = pop @{ $self->{XSStack} }; my $ThisFile = $self->{filename}; my $isPipe = $data->{IsPipe}; --$self->{IncludedFiles}->{$self->{filename}} unless $isPipe; close $self->{FH}; $self->{FH} = $data->{Handle}; # $filename is the leafname, which for some reason is used for diagnostic # messages, whereas $filepathname is the full pathname, and is used for # #line directives. $self->{filename} = $data->{Filename}; $self->{filepathname} = $data->{Filepathname}; $self->{lastline} = $data->{LastLine}; $self->{lastline_no} = $data->{LastLineNo}; @{ $self->{line} } = @{ $data->{Line} }; @{ $self->{line_no} } = @{ $data->{LineNo} }; if ($isPipe and $? ) { --$self->{lastline_no}; print STDERR "Error reading from pipe '$ThisFile': $! in $self->{filename}, line $self->{lastline_no}\n" ; exit 1; } print Q(<<"EOF"); # #/* INCLUDE: Returning to '$self->{filename}' from '$ThisFile' */ # EOF return 1; } sub Q { my($text) = @_; $text =~ s/^#//gm; $text =~ s/\[\[/{/g; $text =~ s/\]\]/}/g; $text; } # Process "MODULE = Foo ..." lines and update global state accordingly sub _process_module_xs_line { my ($self, $module, $pkg, $prefix) = @_; ($self->{Module_cname} = $module) =~ s/\W/_/g; $self->{Package} = defined($pkg) ? $pkg : ''; $self->{Prefix} = quotemeta( defined($prefix) ? $prefix : '' ); ($self->{Packid} = $self->{Package}) =~ tr/:/_/; $self->{Packprefix} = $self->{Package}; $self->{Packprefix} .= "::" if $self->{Packprefix} ne ""; $self->{lastline} = ""; } # Skip any embedded POD sections sub _maybe_skip_pod { my ($self) = @_; while ($self->{lastline} =~ /^=/) { while ($self->{lastline} = readline($self->{FH})) { last if ($self->{lastline} =~ /^=cut\s*$/); } $self->death("Error: Unterminated pod") unless defined $self->{lastline}; $self->{lastline} = readline($self->{FH}); chomp $self->{lastline}; $self->{lastline} =~ s/^\s+$//; } } # This chunk of code strips out (and parses) embedded TYPEMAP blocks # which support a HEREdoc-alike block syntax. sub _maybe_parse_typemap_block { my ($self) = @_; # This is special cased from the usual paragraph-handler logic # due to the HEREdoc-ish syntax. if ($self->{lastline} =~ /^TYPEMAP\s*:\s*<<\s*(?:(["'])(.+?)\1|([^\s'"]+?))\s*;?\s*$/) { my $end_marker = quotemeta(defined($1) ? $2 : $3); # Scan until we find $end_marker alone on a line. my @tmaplines; while (1) { $self->{lastline} = readline($self->{FH}); $self->death("Error: Unterminated TYPEMAP section") if not defined $self->{lastline}; last if $self->{lastline} =~ /^$end_marker\s*$/; push @tmaplines, $self->{lastline}; } my $tmap = ExtUtils::Typemaps->new( string => join("", @tmaplines), lineno_offset => 1 + ($self->current_line_number() || 0), fake_filename => $self->{filename}, ); $self->{typemap}->merge(typemap => $tmap, replace => 1); $self->{lastline} = ""; } } # Read next xsub into @{ $self->{line} } from ($lastline, readline($self->{FH})). sub fetch_para { my $self = shift; # parse paragraph $self->death("Error: Unterminated '#if/#ifdef/#ifndef'") if !defined $self->{lastline} && $self->{XSStack}->[-1]{type} eq 'if'; @{ $self->{line} } = (); @{ $self->{line_no} } = (); return $self->PopFile() if not defined $self->{lastline}; # EOF if ($self->{lastline} =~ /^MODULE\s*=\s*([\w:]+)(?:\s+PACKAGE\s*=\s*([\w:]+))?(?:\s+PREFIX\s*=\s*(\S+))?\s*$/) { $self->_process_module_xs_line($1, $2, $3); } for (;;) { $self->_maybe_skip_pod; $self->_maybe_parse_typemap_block; if ($self->{lastline} !~ /^\s*#/ # not a CPP directive # CPP directives: # ANSI: if ifdef ifndef elif else endif define undef # line error pragma # gcc: warning include_next # obj-c: import # others: ident (gcc notes that some cpps have this one) || $self->{lastline} =~ /^\#[ \t]* (?: (?:if|ifn?def|elif|else|endif| define|undef|pragma|error| warning|line\s+\d+|ident) \b | (?:include(?:_next)?|import) \s* ["<] .* [>"] ) /x ) { last if $self->{lastline} =~ /^\S/ && @{ $self->{line} } && $self->{line}->[-1] eq ""; push(@{ $self->{line} }, $self->{lastline}); push(@{ $self->{line_no} }, $self->{lastline_no}); } # Read next line and continuation lines last unless defined($self->{lastline} = readline($self->{FH})); $self->{lastline_no} = $.; my $tmp_line; $self->{lastline} .= $tmp_line while ($self->{lastline} =~ /\\$/ && defined($tmp_line = readline($self->{FH}))); chomp $self->{lastline}; $self->{lastline} =~ s/^\s+$//; } # Nuke trailing "line" entries until there's one that's not empty pop(@{ $self->{line} }), pop(@{ $self->{line_no} }) while @{ $self->{line} } && $self->{line}->[-1] eq ""; return 1; } sub output_init { my $self = shift; my $argsref = shift; my ($type, $num, $var, $init, $printed_name) = @{$argsref}{qw(type num var init printed_name)}; # local assign for efficiently passing in to eval_input_typemap_code local $argsref->{arg} = $num ? "ST(" . ($num-1) . ")" : "/* not a parameter */"; if ( $init =~ /^=/ ) { if ($printed_name) { $self->eval_input_typemap_code(qq/print " $init\\n"/, $argsref); } else { $self->eval_input_typemap_code(qq/print "\\t$var $init\\n"/, $argsref); } } else { if ( $init =~ s/^\+// && $num ) { $self->generate_init( { type => $type, num => $num, var => $var, printed_name => $printed_name, } ); } elsif ($printed_name) { print ";\n"; $init =~ s/^;//; } else { $self->eval_input_typemap_code(qq/print "\\t$var;\\n"/, $argsref); $init =~ s/^;//; } $self->{deferred} .= $self->eval_input_typemap_code(qq/"\\n\\t$init\\n"/, $argsref); } } sub generate_init { my $self = shift; my $argsref = shift; my ($type, $num, $var, $printed_name) = @{$argsref}{qw(type num var printed_name)}; my $argoff = $num - 1; my $arg = "ST($argoff)"; my $typemaps = $self->{typemap}; $type = ExtUtils::Typemaps::tidy_type($type); if (not $typemaps->get_typemap(ctype => $type)) { $self->report_typemap_failure($typemaps, $type); return; } (my $ntype = $type) =~ s/\s*\*/Ptr/g; (my $subtype = $ntype) =~ s/(?:Array)?(?:Ptr)?$//; my $typem = $typemaps->get_typemap(ctype => $type); my $xstype = $typem->xstype; #this is an optimization from perl 5.0 alpha 6, class check is skipped #T_REF_IV_REF is missing since it has no untyped analog at the moment $xstype =~ s/OBJ$/REF/ || $xstype =~ s/^T_REF_IV_PTR$/T_PTRREF/ if $self->{func_name} =~ /DESTROY$/; if ($xstype eq 'T_PV' and exists $self->{lengthof}->{$var}) { print "\t$var" unless $printed_name; print " = ($type)SvPV($arg, STRLEN_length_of_$var);\n"; die "default value not supported with length(NAME) supplied" if defined $self->{defaults}->{$var}; return; } $type =~ tr/:/_/ unless $self->{RetainCplusplusHierarchicalTypes}; my $inputmap = $typemaps->get_inputmap(xstype => $xstype); if (not defined $inputmap) { $self->blurt("Error: No INPUT definition for type '$type', typekind '" . $type->xstype . "' found"); return; } my $expr = $inputmap->cleaned_code; # Note: This gruesome bit either needs heavy rethinking or documentation. I vote for the former. --Steffen if ($expr =~ /DO_ARRAY_ELEM/) { my $subtypemap = $typemaps->get_typemap(ctype => $subtype); if (not $subtypemap) { $self->report_typemap_failure($typemaps, $subtype); return; } my $subinputmap = $typemaps->get_inputmap(xstype => $subtypemap->xstype); if (not $subinputmap) { $self->blurt("Error: No INPUT definition for type '$subtype', typekind '" . $subtypemap->xstype . "' found"); return; } my $subexpr = $subinputmap->cleaned_code; $subexpr =~ s/\$type/\$subtype/g; $subexpr =~ s/ntype/subtype/g; $subexpr =~ s/\$arg/ST(ix_$var)/g; $subexpr =~ s/\n\t/\n\t\t/g; $subexpr =~ s/is not of (.*\")/[arg %d] is not of $1, ix_$var + 1/g; $subexpr =~ s/\$var/${var}\[ix_$var - $argoff]/; $expr =~ s/DO_ARRAY_ELEM/$subexpr/; } if ($expr =~ m#/\*.*scope.*\*/#i) { # "scope" in C comments $self->{ScopeThisXSUB} = 1; } my $eval_vars = { var => $var, printed_name => $printed_name, type => $type, ntype => $ntype, subtype => $subtype, num => $num, arg => $arg, argoff => $argoff, }; if (defined($self->{defaults}->{$var})) { $expr =~ s/(\t+)/$1 /g; $expr =~ s/ /\t/g; if ($printed_name) { print ";\n"; } else { $self->eval_input_typemap_code(qq/print "\\t$var;\\n"/, $eval_vars); } if ($self->{defaults}->{$var} eq 'NO_INIT') { $self->{deferred} .= $self->eval_input_typemap_code( qq/"\\n\\tif (items >= $num) {\\n$expr;\\n\\t}\\n"/, $eval_vars ); } else { $self->{deferred} .= $self->eval_input_typemap_code( qq/"\\n\\tif (items < $num)\\n\\t $var = $self->{defaults}->{$var};\\n\\telse {\\n$expr;\\n\\t}\\n"/, $eval_vars ); } } elsif ($self->{ScopeThisXSUB} or $expr !~ /^\s*\$var =/) { if ($printed_name) { print ";\n"; } else { $self->eval_input_typemap_code(qq/print "\\t$var;\\n"/, $eval_vars); } $self->{deferred} .= $self->eval_input_typemap_code(qq/"\\n$expr;\\n"/, $eval_vars); } else { die "panic: do not know how to handle this branch for function pointers" if $printed_name; $self->eval_input_typemap_code(qq/print "$expr;\\n"/, $eval_vars); } } sub generate_output { my $self = shift; my $argsref = shift; my ($type, $num, $var, $do_setmagic, $do_push) = @{$argsref}{qw(type num var do_setmagic do_push)}; my $arg = "ST(" . ($num - ($num != 0)) . ")"; my $typemaps = $self->{typemap}; $type = ExtUtils::Typemaps::tidy_type($type); local $argsref->{type} = $type; if ($type =~ /^array\(([^,]*),(.*)\)/) { print "\t$arg = sv_newmortal();\n"; print "\tsv_setpvn($arg, (char *)$var, $2 * sizeof($1));\n"; print "\tSvSETMAGIC($arg);\n" if $do_setmagic; } else { my $typemap = $typemaps->get_typemap(ctype => $type); if (not $typemap) { $self->report_typemap_failure($typemaps, $type); return; } my $outputmap = $typemaps->get_outputmap(xstype => $typemap->xstype); if (not $outputmap) { $self->blurt("Error: No OUTPUT definition for type '$type', typekind '" . $typemap->xstype . "' found"); return; } (my $ntype = $type) =~ s/\s*\*/Ptr/g; $ntype =~ s/\(\)//g; (my $subtype = $ntype) =~ s/(?:Array)?(?:Ptr)?$//; my $eval_vars = {%$argsref, subtype => $subtype, ntype => $ntype, arg => $arg}; my $expr = $outputmap->cleaned_code; if ($expr =~ /DO_ARRAY_ELEM/) { my $subtypemap = $typemaps->get_typemap(ctype => $subtype); if (not $subtypemap) { $self->report_typemap_failure($typemaps, $subtype); return; } my $suboutputmap = $typemaps->get_outputmap(xstype => $subtypemap->xstype); if (not $suboutputmap) { $self->blurt("Error: No OUTPUT definition for type '$subtype', typekind '" . $subtypemap->xstype . "' found"); return; } my $subexpr = $suboutputmap->cleaned_code; $subexpr =~ s/ntype/subtype/g; $subexpr =~ s/\$arg/ST(ix_$var)/g; $subexpr =~ s/\$var/${var}\[ix_$var]/g; $subexpr =~ s/\n\t/\n\t\t/g; $expr =~ s/DO_ARRAY_ELEM\n/$subexpr/; $self->eval_output_typemap_code("print qq\a$expr\a", $eval_vars); print "\t\tSvSETMAGIC(ST(ix_$var));\n" if $do_setmagic; } elsif ($var eq 'RETVAL') { my $orig_arg = $arg; my $indent; my $use_RETVALSV = 1; my $do_mortal = 0; my $do_copy_tmp = 1; my $pre_expr; local $eval_vars->{arg} = $arg = 'RETVALSV'; my $evalexpr = $self->eval_output_typemap_code("qq\a$expr\a", $eval_vars); if ($expr =~ /^\t\Q$arg\E = new/) { # We expect that $arg has refcnt 1, so we need to # mortalize it. $do_mortal = 1; } # If RETVAL is immortal, don't mortalize it. This code is not perfect: # It won't detect a func or expression that only returns immortals, for # example, this RE must be tried before next elsif. elsif ($evalexpr =~ /^\t\Q$arg\E\s*=\s*(boolSV\(|(&PL_sv_yes|&PL_sv_no|&PL_sv_undef)\s*;)/) { $do_copy_tmp = 0; #$arg will be a ST(X), no SV* RETVAL, no RETVALSV $use_RETVALSV = 0; } elsif ($evalexpr =~ /^\s*\Q$arg\E\s*=/) { # We expect that $arg has refcnt >=1, so we need # to mortalize it! $use_RETVALSV = 0 if $ntype eq "SVPtr";#reuse SV* RETVAL vs open new block $do_mortal = 1; } else { # Just hope that the entry would safely write it # over an already mortalized value. By # coincidence, something like $arg = &PL_sv_undef # works too, but should be caught above. $pre_expr = "RETVALSV = sv_newmortal();\n"; # new mortals don't have set magic $do_setmagic = 0; } if($use_RETVALSV) { print "\t{\n\t SV * RETVALSV;\n"; $indent = "\t "; } else { $indent = "\t"; } print $indent.$pre_expr if $pre_expr; if($use_RETVALSV) { #take control of 1 layer of indent, may or may not indent more $evalexpr =~ s/^(\t| )/$indent/gm; #"\t \t" doesn't draw right in some IDEs #break down all \t into spaces $evalexpr =~ s/\t/ /g; #rebuild back into \t'es, \t==8 spaces, indent==4 spaces $evalexpr =~ s/ /\t/g; } else { if($do_mortal || $do_setmagic) { #typemap entry evaled with RETVALSV, if we aren't using RETVALSV replace $evalexpr =~ s/RETVALSV/RETVAL/g; #all uses with RETVAL for prettier code } else { #if no extra boilerplate (no mortal, no set magic) is needed #after $evalexport, get rid of RETVALSV's visual cluter and change $evalexpr =~ s/RETVALSV/$orig_arg/g;#the lvalue to ST(X) } } #stop " RETVAL = RETVAL;" for SVPtr type print $evalexpr if $evalexpr !~ /^\s*RETVAL = RETVAL;$/; print $indent.'RETVAL'.($use_RETVALSV ? 'SV':'') .' = sv_2mortal(RETVAL'.($use_RETVALSV ? 'SV':'').");\n" if $do_mortal; print $indent.'SvSETMAGIC(RETVAL'.($use_RETVALSV ? 'SV':'').");\n" if $do_setmagic; #dont do "RETVALSV = boolSV(RETVAL); ST(0) = RETVALSV;", it is visual clutter print $indent."$orig_arg = RETVAL".($use_RETVALSV ? 'SV':'').";\n" if $do_mortal || $do_setmagic || $do_copy_tmp; print "\t}\n" if $use_RETVALSV; } elsif ($do_push) { print "\tPUSHs(sv_newmortal());\n"; local $eval_vars->{arg} = "ST($num)"; $self->eval_output_typemap_code("print qq\a$expr\a", $eval_vars); print "\tSvSETMAGIC($arg);\n" if $do_setmagic; } elsif ($arg =~ /^ST\(\d+\)$/) { $self->eval_output_typemap_code("print qq\a$expr\a", $eval_vars); print "\tSvSETMAGIC($arg);\n" if $do_setmagic; } } } # Just delegates to a clean package. # Shim to evaluate Perl code in the right variable context # for typemap code (having things such as $ALIAS set up). sub eval_output_typemap_code { my ($self, $code, $other) = @_; return ExtUtils::ParseXS::Eval::eval_output_typemap_code($self, $code, $other); } sub eval_input_typemap_code { my ($self, $code, $other) = @_; return ExtUtils::ParseXS::Eval::eval_input_typemap_code($self, $code, $other); } 1; # vim: ts=2 sw=2 et: package ExtUtils::MM_VMS; use strict; use ExtUtils::MakeMaker::Config; require Exporter; BEGIN { # so we can compile the thing on non-VMS platforms. if( $^O eq 'VMS' ) { require VMS::Filespec; VMS::Filespec->import; } } use File::Basename; our $VERSION = '7.34'; $VERSION = eval $VERSION; require ExtUtils::MM_Any; require ExtUtils::MM_Unix; our @ISA = qw( ExtUtils::MM_Any ExtUtils::MM_Unix ); use ExtUtils::MakeMaker qw($Verbose neatvalue _sprintf562); our $Revision = $ExtUtils::MakeMaker::Revision; sub wraplist { my($self) = shift; my($line,$hlen) = ('',0); foreach my $word (@_) { # Perl bug -- seems to occasionally insert extra elements when # traversing array (scalar(@array) doesn't show them, but # foreach(@array) does) (5.00307) next unless $word =~ /\w/; $line .= ' ' if length($line); if ($hlen > 80) { $line .= "\\\n\t"; $hlen = 0; } $line .= $word; $hlen += length($word) + 2; } $line; } # This isn't really an override. It's just here because ExtUtils::MM_VMS # appears in @MM::ISA before ExtUtils::Liblist::Kid, so if there isn't an ext() # in MM_VMS, then AUTOLOAD is called, and bad things happen. So, we just # mimic inheritance here and hand off to ExtUtils::Liblist::Kid. # XXX This hackery will die soon. --Schwern sub ext { require ExtUtils::Liblist::Kid; goto &ExtUtils::Liblist::Kid::ext; } sub guess_name { my($self) = @_; my($defname,$defpm,@pm,%xs); local *PM; $defname = basename(fileify($ENV{'DEFAULT'})); $defname =~ s![\d\-_]*\.dir.*$!!; # Clip off .dir;1 suffix, and package version $defpm = $defname; # Fallback in case for some reason a user has copied the files for an # extension into a working directory whose name doesn't reflect the # extension's name. We'll use the name of a unique .pm file, or the # first .pm file with a matching .xs file. if (not -e "${defpm}.pm") { @pm = glob('*.pm'); s/.pm$// for @pm; if (@pm == 1) { ($defpm = $pm[0]) =~ s/.pm$//; } elsif (@pm) { %xs = map { s/.xs$//; ($_,1) } glob('*.xs'); ## no critic if (keys %xs) { foreach my $pm (@pm) { $defpm = $pm, last if exists $xs{$pm}; } } } } if (open(my $pm, '<', "${defpm}.pm")){ while (<$pm>) { if (/^\s*package\s+([^;]+)/i) { $defname = $1; last; } } print "Warning (non-fatal): Couldn't find package name in ${defpm}.pm;\n\t", "defaulting package name to $defname\n" if eof($pm); close $pm; } else { print "Warning (non-fatal): Couldn't find ${defpm}.pm;\n\t", "defaulting package name to $defname\n"; } $defname =~ s#[\d.\-_]+$##; $defname; } sub find_perl { my($self, $ver, $names, $dirs, $trace) = @_; my($vmsfile,@sdirs,@snames,@cand); my($rslt); my($inabs) = 0; local *TCF; if( $self->{PERL_CORE} ) { # Check in relative directories first, so we pick up the current # version of Perl if we're running MakeMaker as part of the main build. @sdirs = sort { my($absa) = $self->file_name_is_absolute($a); my($absb) = $self->file_name_is_absolute($b); if ($absa && $absb) { return $a cmp $b } else { return $absa ? 1 : ($absb ? -1 : ($a cmp $b)); } } @$dirs; # Check miniperl before perl, and check names likely to contain # version numbers before "generic" names, so we pick up an # executable that's less likely to be from an old installation. @snames = sort { my($ba) = $a =~ m!([^:>\]/]+)$!; # basename my($bb) = $b =~ m!([^:>\]/]+)$!; my($ahasdir) = (length($a) - length($ba) > 0); my($bhasdir) = (length($b) - length($bb) > 0); if ($ahasdir and not $bhasdir) { return 1; } elsif ($bhasdir and not $ahasdir) { return -1; } else { $bb =~ /\d/ <=> $ba =~ /\d/ or substr($ba,0,1) cmp substr($bb,0,1) or length($bb) <=> length($ba) } } @$names; } else { @sdirs = @$dirs; @snames = @$names; } # Image names containing Perl version use '_' instead of '.' under VMS s/\.(\d+)$/_$1/ for @snames; if ($trace >= 2){ print "Looking for perl $ver by these names:\n"; print "\t@snames,\n"; print "in these dirs:\n"; print "\t@sdirs\n"; } foreach my $dir (@sdirs){ next unless defined $dir; # $self->{PERL_SRC} may be undefined $inabs++ if $self->file_name_is_absolute($dir); if ($inabs == 1) { # We've covered relative dirs; everything else is an absolute # dir (probably an installed location). First, we'll try # potential command names, to see whether we can avoid a long # MCR expression. foreach my $name (@snames) { push(@cand,$name) if $name =~ /^[\w\-\$]+$/; } $inabs++; # Should happen above in next $dir, but just in case... } foreach my $name (@snames){ push @cand, ($name !~ m![/:>\]]!) ? $self->catfile($dir,$name) : $self->fixpath($name,0); } } foreach my $name (@cand) { print "Checking $name\n" if $trace >= 2; # If it looks like a potential command, try it without the MCR if ($name =~ /^[\w\-\$]+$/) { open(my $tcf, ">", "temp_mmvms.com") or die('unable to open temp file'); print $tcf "\$ set message/nofacil/nosever/noident/notext\n"; print $tcf "\$ $name -e \"require $ver; print \"\"VER_OK\\n\"\"\"\n"; close $tcf; $rslt = `\@temp_mmvms.com` ; unlink('temp_mmvms.com'); if ($rslt =~ /VER_OK/) { print "Using PERL=$name\n" if $trace; return $name; } } next unless $vmsfile = $self->maybe_command($name); $vmsfile =~ s/;[\d\-]*$//; # Clip off version number; we can use a newer version as well print "Executing $vmsfile\n" if ($trace >= 2); open(my $tcf, '>', "temp_mmvms.com") or die('unable to open temp file'); print $tcf "\$ set message/nofacil/nosever/noident/notext\n"; print $tcf "\$ mcr $vmsfile -e \"require $ver; print \"\"VER_OK\\n\"\"\" \n"; close $tcf; $rslt = `\@temp_mmvms.com`; unlink('temp_mmvms.com'); if ($rslt =~ /VER_OK/) { print "Using PERL=MCR $vmsfile\n" if $trace; return "MCR $vmsfile"; } } print "Unable to find a perl $ver (by these names: @$names, in these dirs: @$dirs)\n"; 0; # false and not empty } sub _fixin_replace_shebang { my ( $self, $file, $line ) = @_; my ( undef, $arg ) = split ' ', $line, 2; return $Config{startperl} . "\n" . $Config{sharpbang} . "perl $arg\n"; } sub maybe_command { my($self,$file) = @_; return $file if -x $file && ! -d _; my(@dirs) = (''); my(@exts) = ('',$Config{'exe_ext'},'.exe','.com'); if ($file !~ m![/:>\]]!) { for (my $i = 0; defined $ENV{"DCL\$PATH;$i"}; $i++) { my $dir = $ENV{"DCL\$PATH;$i"}; $dir .= ':' unless $dir =~ m%[\]:]$%; push(@dirs,$dir); } push(@dirs,'Sys$System:'); foreach my $dir (@dirs) { my $sysfile = "$dir$file"; foreach my $ext (@exts) { return $file if -x "$sysfile$ext" && ! -d _; } } } return 0; } sub pasthru { my($self) = shift; my $pasthru = $self->SUPER::pasthru; $pasthru =~ s|(PASTHRU\s*=\s*)|$1/MACRO=(|; $pasthru =~ s|\n\z|)\n|m; $pasthru =~ s|/defi?n?e?=\(?([^\),]+)\)?|,$1|ig; return $pasthru; } sub pm_to_blib { my $self = shift; my $make = $self->SUPER::pm_to_blib; $make =~ s{^pm_to_blib :}{pm_to_blib.ts :}m; $make =~ s{\$\(TOUCH\) pm_to_blib}{\$(TOUCH) pm_to_blib.ts}; $make = <<'MAKE' . $make; # Dummy target to match Unix target name; we use pm_to_blib.ts as # timestamp file to avoid repeated invocations under VMS pm_to_blib : pm_to_blib.ts $(NOECHO) $(NOOP) MAKE return $make; } sub perl_script { my($self,$file) = @_; return $file if -r $file && ! -d _; return "$file.com" if -r "$file.com"; return "$file.pl" if -r "$file.pl"; return ''; } sub replace_manpage_separator { my($self,$man) = @_; $man = unixify($man); $man =~ s#/+#__#g; $man; } sub init_DEST { my $self = shift; $self->SUPER::init_DEST; # Expand DEST variables. foreach my $var ($self->installvars) { my $destvar = 'DESTINSTALL'.$var; $self->{$destvar} = $self->eliminate_macros($self->{$destvar}); } } sub init_DIRFILESEP { my($self) = shift; $self->{DIRFILESEP} = ''; return 1; } sub init_main { my($self) = shift; $self->SUPER::init_main; $self->{DEFINE} ||= ''; if ($self->{DEFINE} ne '') { my(@terms) = split(/\s+/,$self->{DEFINE}); my(@defs,@udefs); foreach my $def (@terms) { next unless $def; my $targ = \@defs; if ($def =~ s/^-([DU])//) { # If it was a Unix-style definition $targ = \@udefs if $1 eq 'U'; $def =~ s/='(.*)'$/=$1/; # then remove shell-protection '' $def =~ s/^'(.*)'$/$1/; # from entire term or argument } if ($def =~ /=/) { $def =~ s/"/""/g; # Protect existing " from DCL $def = qq["$def"]; # and quote to prevent parsing of = } push @$targ, $def; } $self->{DEFINE} = ''; if (@defs) { $self->{DEFINE} = '/Define=(' . join(',',@defs) . ')'; } if (@udefs) { $self->{DEFINE} .= '/Undef=(' . join(',',@udefs) . ')'; } } } sub init_tools { my($self) = @_; $self->{NOOP} = 'Continue'; $self->{NOECHO} ||= '@ '; $self->{MAKEFILE} ||= $self->{FIRST_MAKEFILE} || 'Descrip.MMS'; $self->{FIRST_MAKEFILE} ||= $self->{MAKEFILE}; $self->{MAKE_APERL_FILE} ||= 'Makeaperl.MMS'; $self->{MAKEFILE_OLD} ||= $self->eliminate_macros('$(FIRST_MAKEFILE)_old'); # # If an extension is not specified, then MMS/MMK assumes an # an extension of .MMS. If there really is no extension, # then a trailing "." needs to be appended to specify a # a null extension. # $self->{MAKEFILE} .= '.' unless $self->{MAKEFILE} =~ m/\./; $self->{FIRST_MAKEFILE} .= '.' unless $self->{FIRST_MAKEFILE} =~ m/\./; $self->{MAKE_APERL_FILE} .= '.' unless $self->{MAKE_APERL_FILE} =~ m/\./; $self->{MAKEFILE_OLD} .= '.' unless $self->{MAKEFILE_OLD} =~ m/\./; $self->{MACROSTART} ||= '/Macro=('; $self->{MACROEND} ||= ')'; $self->{USEMAKEFILE} ||= '/Descrip='; $self->{EQUALIZE_TIMESTAMP} ||= '$(ABSPERLRUN) -we "open F,qq{>>$ARGV[1]};close F;utime(0,(stat($ARGV[0]))[9]+1,$ARGV[1])"'; $self->{MOD_INSTALL} ||= $self->oneliner(<<'CODE', ['-MExtUtils::Install']); install([ from_to => {split('\|', )}, verbose => '$(VERBINST)', uninstall_shadows => '$(UNINST)', dir_mode => '$(PERM_DIR)' ]); CODE $self->{UMASK_NULL} = '! '; $self->SUPER::init_tools; # Use the default shell $self->{SHELL} ||= 'Posix'; # Redirection on VMS goes before the command, not after as on Unix. # $(DEV_NULL) is used once and its not worth going nuts over making # it work. However, Unix's DEV_NULL is quite wrong for VMS. $self->{DEV_NULL} = ''; return; } sub init_platform { my($self) = shift; $self->{MM_VMS_REVISION} = $Revision; $self->{MM_VMS_VERSION} = $VERSION; $self->{PERL_VMS} = $self->catdir($self->{PERL_SRC}, 'VMS') if $self->{PERL_SRC}; } sub platform_constants { my($self) = shift; my $make_frag = ''; foreach my $macro (qw(PERL_VMS MM_VMS_REVISION MM_VMS_VERSION)) { next unless defined $self->{$macro}; $make_frag .= "$macro = $self->{$macro}\n"; } return $make_frag; } sub init_VERSION { my $self = shift; $self->SUPER::init_VERSION; $self->{DEFINE_VERSION} = '"$(VERSION_MACRO)=""$(VERSION)"""'; $self->{XS_DEFINE_VERSION} = '"$(XS_VERSION_MACRO)=""$(XS_VERSION)"""'; $self->{MAKEMAKER} = vmsify($INC{'ExtUtils/MakeMaker.pm'}); } sub constants { my($self) = @_; # Be kind about case for pollution for (@ARGV) { $_ = uc($_) if /POLLUTE/i; } # Cleanup paths for directories in MMS macros. foreach my $macro ( qw [ INST_BIN INST_SCRIPT INST_LIB INST_ARCHLIB PERL_LIB PERL_ARCHLIB PERL_INC PERL_SRC ], (map { 'INSTALL'.$_ } $self->installvars) ) { next unless defined $self->{$macro}; next if $macro =~ /MAN/ && $self->{$macro} eq 'none'; $self->{$macro} = $self->fixpath($self->{$macro},1); } # Cleanup paths for files in MMS macros. foreach my $macro ( qw[LIBPERL_A FIRST_MAKEFILE MAKEFILE_OLD MAKE_APERL_FILE MYEXTLIB] ) { next unless defined $self->{$macro}; $self->{$macro} = $self->fixpath($self->{$macro},0); } # Fixup files for MMS macros # XXX is this list complete? for my $macro (qw/ FULLEXT VERSION_FROM / ) { next unless defined $self->{$macro}; $self->{$macro} = $self->fixpath($self->{$macro},0); } for my $macro (qw/ OBJECT LDFROM / ) { next unless defined $self->{$macro}; # Must expand macros before splitting on unescaped whitespace. $self->{$macro} = $self->eliminate_macros($self->{$macro}); if ($self->{$macro} =~ /(?{$macro} =~ s/(\\)?\n+\s+/ /g; $self->{$macro} = $self->wraplist( map $self->fixpath($_,0), split /,?(?{$macro} ); } else { $self->{$macro} = $self->fixpath($self->{$macro},0); } } for my $macro (qw/ XS MAN1PODS MAN3PODS PM /) { # Where is the space coming from? --jhi next unless $self ne " " && defined $self->{$macro}; my %tmp = (); for my $key (keys %{$self->{$macro}}) { $tmp{$self->fixpath($key,0)} = $self->fixpath($self->{$macro}{$key},0); } $self->{$macro} = \%tmp; } for my $macro (qw/ C O_FILES H /) { next unless defined $self->{$macro}; my @tmp = (); for my $val (@{$self->{$macro}}) { push(@tmp,$self->fixpath($val,0)); } $self->{$macro} = \@tmp; } # mms/k does not define a $(MAKE) macro. $self->{MAKE} = '$(MMS)$(MMSQUALIFIERS)'; return $self->SUPER::constants; } sub special_targets { my $self = shift; my $make_frag .= <<'MAKE_FRAG'; .SUFFIXES : .SUFFIXES : $(OBJ_EXT) .c .cpp .cxx .xs MAKE_FRAG return $make_frag; } sub cflags { my($self,$libperl) = @_; my($quals) = $self->{CCFLAGS} || $Config{'ccflags'}; my($definestr,$undefstr,$flagoptstr) = ('','',''); my($incstr) = '/Include=($(PERL_INC)'; my($name,$sys,@m); ( $name = $self->{NAME} . "_cflags" ) =~ s/:/_/g ; print "Unix shell script ".$Config{"$self->{'BASEEXT'}_cflags"}. " required to modify CC command for $self->{'BASEEXT'}\n" if ($Config{$name}); if ($quals =~ / -[DIUOg]/) { while ($quals =~ / -([Og])(\d*)\b/) { my($type,$lvl) = ($1,$2); $quals =~ s/ -$type$lvl\b\s*//; if ($type eq 'g') { $flagoptstr = '/NoOptimize'; } else { $flagoptstr = '/Optimize' . (defined($lvl) ? "=$lvl" : ''); } } while ($quals =~ / -([DIU])(\S+)/) { my($type,$def) = ($1,$2); $quals =~ s/ -$type$def\s*//; $def =~ s/"/""/g; if ($type eq 'D') { $definestr .= qq["$def",]; } elsif ($type eq 'I') { $incstr .= ',' . $self->fixpath($def,1); } else { $undefstr .= qq["$def",]; } } } if (length $quals and $quals !~ m!/!) { warn "MM_VMS: Ignoring unrecognized CCFLAGS elements \"$quals\"\n"; $quals = ''; } $definestr .= q["PERL_POLLUTE",] if $self->{POLLUTE}; if (length $definestr) { chop($definestr); $quals .= "/Define=($definestr)"; } if (length $undefstr) { chop($undefstr); $quals .= "/Undef=($undefstr)"; } # Deal with $self->{DEFINE} here since some C compilers pay attention # to only one /Define clause on command line, so we have to # conflate the ones from $Config{'ccflags'} and $self->{DEFINE} # ($self->{DEFINE} has already been VMSified in constants() above) if ($self->{DEFINE}) { $quals .= $self->{DEFINE}; } for my $type (qw(Def Undef)) { my(@terms); while ($quals =~ m:/${type}i?n?e?=([^/]+):ig) { my $term = $1; $term =~ s:^\((.+)\)$:$1:; push @terms, $term; } if ($type eq 'Def') { push @terms, qw[ $(DEFINE_VERSION) $(XS_DEFINE_VERSION) ]; } if (@terms) { $quals =~ s:/${type}i?n?e?=[^/]+::ig; # PASTHRU_DEFINE will have its own comma $quals .= "/${type}ine=(" . join(',',@terms) . ($type eq 'Def' ? '$(PASTHRU_DEFINE)' : '') . ')'; } } $libperl or $libperl = $self->{LIBPERL_A} || "libperl.olb"; # Likewise with $self->{INC} and /Include if ($self->{'INC'}) { my(@includes) = split(/\s+/,$self->{INC}); foreach (@includes) { s/^-I//; $incstr .= ','.$self->fixpath($_,1); } } $quals .= "$incstr)"; # $quals =~ s/,,/,/g; $quals =~ s/\(,/(/g; $self->{CCFLAGS} = $quals; $self->{PERLTYPE} ||= ''; $self->{OPTIMIZE} ||= $flagoptstr || $Config{'optimize'}; if ($self->{OPTIMIZE} !~ m!/!) { if ($self->{OPTIMIZE} =~ m!-g!) { $self->{OPTIMIZE} = '/Debug/NoOptimize' } elsif ($self->{OPTIMIZE} =~ /-O(\d*)/) { $self->{OPTIMIZE} = '/Optimize' . (defined($1) ? "=$1" : ''); } else { warn "MM_VMS: Can't parse OPTIMIZE \"$self->{OPTIMIZE}\"; using default\n" if length $self->{OPTIMIZE}; $self->{OPTIMIZE} = '/Optimize'; } } return $self->{CFLAGS} = qq{ CCFLAGS = $self->{CCFLAGS} OPTIMIZE = $self->{OPTIMIZE} PERLTYPE = $self->{PERLTYPE} }; } sub const_cccmd { my($self,$libperl) = @_; my(@m); return $self->{CONST_CCCMD} if $self->{CONST_CCCMD}; return '' unless $self->needs_linking(); if ($Config{'vms_cc_type'} eq 'gcc') { push @m,' .FIRST ',$self->{NOECHO},'If F$TrnLnm("Sys").eqs."" Then Define/NoLog SYS GNU_CC_Include:[VMS]'; } elsif ($Config{'vms_cc_type'} eq 'vaxc') { push @m,' .FIRST ',$self->{NOECHO},'If F$TrnLnm("Sys").eqs."" .and. F$TrnLnm("VAXC$Include").eqs."" Then Define/NoLog SYS Sys$Library ',$self->{NOECHO},'If F$TrnLnm("Sys").eqs."" .and. F$TrnLnm("VAXC$Include").nes."" Then Define/NoLog SYS VAXC$Include'; } else { push @m,' .FIRST ',$self->{NOECHO},'If F$TrnLnm("Sys").eqs."" .and. F$TrnLnm("DECC$System_Include").eqs."" Then Define/NoLog SYS ', ($Config{'archname'} eq 'VMS_AXP' ? 'Sys$Library' : 'DECC$Library_Include'),' ',$self->{NOECHO},'If F$TrnLnm("Sys").eqs."" .and. F$TrnLnm("DECC$System_Include").nes."" Then Define/NoLog SYS DECC$System_Include'; } push(@m, "\n\nCCCMD = $Config{'cc'} \$(CCFLAGS)\$(OPTIMIZE)\n"); $self->{CONST_CCCMD} = join('',@m); } sub tools_other { my($self) = @_; # XXX Are these necessary? Does anyone override them? They're longer # than just typing the literal string. my $extra_tools = <<'EXTRA_TOOLS'; # Just in case anyone is using the old macro. USEMACROS = $(MACROSTART) SAY = $(ECHO) EXTRA_TOOLS return $self->SUPER::tools_other . $extra_tools; } sub init_dist { my($self) = @_; $self->{ZIPFLAGS} ||= '-Vu'; $self->{COMPRESS} ||= 'gzip'; $self->{SUFFIX} ||= '-gz'; $self->{SHAR} ||= 'vms_share'; $self->{DIST_DEFAULT} ||= 'zipdist'; $self->SUPER::init_dist; $self->{DISTVNAME} = "$self->{DISTNAME}-$self->{VERSION_SYM}" unless $self->{ARGS}{DISTVNAME}; return; } sub c_o { my($self) = @_; return '' unless $self->needs_linking(); ' .c$(OBJ_EXT) : $(CCCMD) $(CCCDLFLAGS) $(MMS$TARGET_NAME).c /OBJECT=$(MMS$TARGET_NAME)$(OBJ_EXT) .cpp$(OBJ_EXT) : $(CCCMD) $(CCCDLFLAGS) $(MMS$TARGET_NAME).cpp /OBJECT=$(MMS$TARGET_NAME)$(OBJ_EXT) .cxx$(OBJ_EXT) : $(CCCMD) $(CCCDLFLAGS) $(MMS$TARGET_NAME).cxx /OBJECT=$(MMS$TARGET_NAME)$(OBJ_EXT) '; } sub xs_c { my($self) = @_; return '' unless $self->needs_linking(); ' .xs.c : $(XSUBPPRUN) $(XSPROTOARG) $(XSUBPPARGS) $(MMS$TARGET_NAME).xs >$(MMS$TARGET_NAME).xsc $(MV) $(MMS$TARGET_NAME).xsc $(MMS$TARGET_NAME).c '; } sub xs_o { my ($self) = @_; return '' unless $self->needs_linking(); my $frag = ' .xs$(OBJ_EXT) : $(XSUBPPRUN) $(XSPROTOARG) $(XSUBPPARGS) $(MMS$TARGET_NAME).xs >$(MMS$TARGET_NAME).xsc $(MV) $(MMS$TARGET_NAME).xsc $(MMS$TARGET_NAME).c $(CCCMD) $(CCCDLFLAGS) $(MMS$TARGET_NAME).c /OBJECT=$(MMS$TARGET_NAME)$(OBJ_EXT) '; if ($self->{XSMULTI}) { for my $ext ($self->_xs_list_basenames) { my $version = $self->parse_version("$ext.pm"); my $ccflags = $self->{CCFLAGS}; $ccflags =~ s/\$\(DEFINE_VERSION\)/\"VERSION_MACRO=\\"\"$version\\"\"/; $ccflags =~ s/\$\(XS_DEFINE_VERSION\)/\"XS_VERSION_MACRO=\\"\"$version\\"\"/; $self->_xsbuild_replace_macro($ccflags, 'xs', $ext, 'INC'); $self->_xsbuild_replace_macro($ccflags, 'xs', $ext, 'DEFINE'); $frag .= _sprintf562 <<'EOF', $ext, $ccflags; %1$s$(OBJ_EXT) : %1$s.xs $(XSUBPPRUN) $(XSPROTOARG) $(XSUBPPARGS) $(MMS$TARGET_NAME).xs > $(MMS$TARGET_NAME).xsc $(MV) $(MMS$TARGET_NAME).xsc $(MMS$TARGET_NAME).c $(CC)%2$s$(OPTIMIZE) $(CCCDLFLAGS) $(MMS$TARGET_NAME).c /OBJECT=$(MMS$TARGET_NAME)$(OBJ_EXT) EOF } } $frag; } sub _xsbuild_replace_macro { my ($self, undef, $xstype, $ext, $varname) = @_; my $value = $self->_xsbuild_value($xstype, $ext, $varname); return unless defined $value; $_[1] = _vms_replace_qualifier($self, $_[1], $value, $varname); } sub _xsbuild_value { my ($self, $xstype, $ext, $varname) = @_; $ext = unixify($ext); return $self->SUPER::_xsbuild_value($xstype, $ext, $varname); } sub _vms_replace_qualifier { my ($self, $flags, $newflag, $macro) = @_; my $qual_type; my $type_suffix; my $quote_subquals = 0; my @subquals_new = split /\s+/, $newflag; if ($macro eq 'DEFINE') { $qual_type = 'Def'; $type_suffix = 'ine'; map { $_ =~ s/^-D// } @subquals_new; $quote_subquals = 1; } elsif ($macro eq 'INC') { $qual_type = 'Inc'; $type_suffix = 'lude'; map { $_ =~ s/^-I//; $_ = $self->fixpath($_) } @subquals_new; } my @subquals = (); while ($flags =~ m:/${qual_type}\S{0,4}=([^/]+):ig) { my $term = $1; $term =~ s/\"//g; $term =~ s:^\((.+)\)$:$1:; push @subquals, split /,/, $term; } for my $new (@subquals_new) { my ($sq_new, $sqval_new) = split /=/, $new; my $replaced_old = 0; for my $old (@subquals) { my ($sq, $sqval) = split /=/, $old; if ($sq_new eq $sq) { $old = $sq_new; $old .= '=' . $sqval_new if defined($sqval_new) and length($sqval_new); $replaced_old = 1; last; } } push @subquals, $new unless $replaced_old; } if (@subquals) { $flags =~ s:/${qual_type}\S{0,4}=[^/]+::ig; # add quotes if requested but not for unexpanded macros map { $_ = qq/"$_"/ if $_ !~ m/^\$\(/ } @subquals if $quote_subquals; $flags .= "/${qual_type}$type_suffix=(" . join(',',@subquals) . ')'; } return $flags; } sub xs_dlsyms_ext { '.opt'; } sub dlsyms { my ($self, %attribs) = @_; return '' unless $self->needs_linking; $self->xs_dlsyms_iterator; } sub xs_make_dlsyms { my ($self, $attribs, $target, $dep, $name, $dlbase, $funcs, $funclist, $imports, $vars, $extra) = @_; my @m; my $instloc; if ($self->{XSMULTI}) { my ($v, $d, $f) = File::Spec->splitpath($target); my @d = File::Spec->splitdir($d); shift @d if $d[0] eq 'lib'; $instloc = $self->catfile('$(INST_ARCHLIB)', 'auto', @d, $f); push @m,"\ndynamic :: $instloc\n\t\$(NOECHO) \$(NOOP)\n" unless $self->{SKIPHASH}{'dynamic'}; push @m,"\nstatic :: $instloc\n\t\$(NOECHO) \$(NOOP)\n" unless $self->{SKIPHASH}{'static'}; push @m, "\n", sprintf <<'EOF', $instloc, $target; %s : %s $(CP) $(MMS$SOURCE) $(MMS$TARGET) EOF } else { push @m,"\ndynamic :: \$(INST_ARCHAUTODIR)$self->{BASEEXT}.opt\n\t\$(NOECHO) \$(NOOP)\n" unless $self->{SKIPHASH}{'dynamic'}; push @m,"\nstatic :: \$(INST_ARCHAUTODIR)$self->{BASEEXT}.opt\n\t\$(NOECHO) \$(NOOP)\n" unless $self->{SKIPHASH}{'static'}; push @m, "\n", sprintf <<'EOF', $target; $(INST_ARCHAUTODIR)$(BASEEXT).opt : %s $(CP) $(MMS$SOURCE) $(MMS$TARGET) EOF } push @m, "\n$target : $dep\n\t", q!$(PERLRUN) -MExtUtils::Mksymlists -e "Mksymlists('NAME'=>'!, $name, q!', 'DLBASE' => '!,$dlbase, q!', 'DL_FUNCS' => !,neatvalue($funcs), q!, 'FUNCLIST' => !,neatvalue($funclist), q!, 'IMPORTS' => !,neatvalue($imports), q!, 'DL_VARS' => !, neatvalue($vars); push @m, $extra if defined $extra; push @m, qq!);"\n\t!; # Can't use dlbase as it's been through mod2fname. my $olb_base = basename($target, '.opt'); if ($self->{XSMULTI}) { # We've been passed everything but the kitchen sink -- and the location of the # static library we're using to build the dynamic library -- so concoct that # location from what we do have. my $olb_dir = $self->catdir(dirname($instloc), $olb_base); push @m, qq!\$(PERL) -e "print ""${olb_dir}${olb_base}\$(LIB_EXT)/Include=!; push @m, ($Config{d_vms_case_sensitive_symbols} ? uc($olb_base) : $olb_base); push @m, '\n' . $olb_dir . $olb_base . '$(LIB_EXT)/Library\n"";" >>$(MMS$TARGET)',"\n"; } else { push @m, qq!\$(PERL) -e "print ""\$(INST_ARCHAUTODIR)${olb_base}\$(LIB_EXT)/Include=!; if ($self->{OBJECT} =~ /\bBASEEXT\b/ or $self->{OBJECT} =~ /\b$self->{BASEEXT}\b/i) { push @m, ($Config{d_vms_case_sensitive_symbols} ? uc($self->{BASEEXT}) :'$(BASEEXT)'); } else { # We don't have a "main" object file, so pull 'em all in # Upcase module names if linker is being case-sensitive my($upcase) = $Config{d_vms_case_sensitive_symbols}; my(@omods) = split ' ', $self->eliminate_macros($self->{OBJECT}); for (@omods) { s/\.[^.]*$//; # Trim off file type s[\$\(\w+_EXT\)][]; # even as a macro s/.*[:>\/\]]//; # Trim off dir spec $_ = uc if $upcase; }; my(@lines); my $tmp = shift @omods; foreach my $elt (@omods) { $tmp .= ",$elt"; if (length($tmp) > 80) { push @lines, $tmp; $tmp = ''; } } push @lines, $tmp; push @m, '(', join( qq[, -\\n\\t"";" >>\$(MMS\$TARGET)\n\t\$(PERL) -e "print ""], @lines),')'; } push @m, '\n$(INST_ARCHAUTODIR)' . $olb_base . '$(LIB_EXT)/Library\n"";" >>$(MMS$TARGET)',"\n"; } if (length $self->{LDLOADLIBS}) { my($line) = ''; foreach my $lib (split ' ', $self->{LDLOADLIBS}) { $lib =~ s%\$%\\\$%g; # Escape '$' in VMS filespecs if (length($line) + length($lib) > 160) { push @m, "\t\$(PERL) -e \"print qq{$line}\" >>\$(MMS\$TARGET)\n"; $line = $lib . '\n'; } else { $line .= $lib . '\n'; } } push @m, "\t\$(PERL) -e \"print qq{$line}\" >>\$(MMS\$TARGET)\n" if $line; } join '', @m; } sub xs_obj_opt { my ($self, $output_file) = @_; "/OBJECT=$output_file"; } sub xs_dynamic_lib_macros { my ($self, $attribs) = @_; my $otherldflags = $attribs->{OTHERLDFLAGS} || ""; my $inst_dynamic_dep = $attribs->{INST_DYNAMIC_DEP} || ""; sprintf <<'EOF', $otherldflags, $inst_dynamic_dep; # This section creates the dynamically loadable objects from relevant # objects and possibly $(MYEXTLIB). OTHERLDFLAGS = %s INST_DYNAMIC_DEP = %s EOF } sub xs_make_dynamic_lib { my ($self, $attribs, $from, $to, $todir, $ldfrom, $exportlist) = @_; my $shr = $Config{'dbgprefix'} . 'PerlShr'; $exportlist =~ s/.def$/.opt/; # it's a linker options file # 1 2 3 4 5 _sprintf562 <<'EOF', $to, $todir, $exportlist, $shr, "$shr Sys\$Share:$shr.$Config{'dlext'}"; %1$s : $(INST_STATIC) $(PERL_INC)perlshr_attr.opt %2$s$(DFSEP).exists %3$s $(PERL_ARCHIVE) $(INST_DYNAMIC_DEP) If F$TrnLNm("%4$s").eqs."" Then Define/NoLog/User %5$s Link $(LDFLAGS) /Shareable=$(MMS$TARGET)$(OTHERLDFLAGS) %3$s/Option,$(PERL_INC)perlshr_attr.opt/Option EOF } sub xs_make_static_lib { my ($self, $object, $to, $todir) = @_; my @objects; if ($self->{XSMULTI}) { # The extension name should be the main object file name minus file type. my $lib = $object; $lib =~ s/\$\(OBJ_EXT\)\z//; my $override = $self->_xsbuild_value('xs', $lib, 'OBJECT'); $object = $override if defined $override; @objects = map { $self->fixpath($_,0) } split /(?{MYEXTLIB}; push(@m,"\t",'If F$Search("$(MMS$TARGET)").eqs."" Then Library/Object/Create $(MMS$TARGET)',"\n"); # if there was a library to copy, then we can't use MMS$SOURCE_LIST, # 'cause it's a library and you can't stick them in other libraries. # In that case, we use $OBJECT instead and hope for the best if ($self->{MYEXTLIB}) { for my $obj (@objects) { push(@m,"\t",'Library/Object/Replace $(MMS$TARGET) ' . $obj,"\n"); } } else { push(@m,"\t",'Library/Object/Replace $(MMS$TARGET) $(MMS$SOURCE_LIST)',"\n"); } push @m, "\t\$(NOECHO) \$(PERL) -e 1 >\$(INST_ARCHAUTODIR)extralibs.ld\n"; foreach my $lib (split ' ', $self->{EXTRALIBS}) { push(@m,"\t",'$(NOECHO) $(PERL) -e "print qq{',$lib,'\n}" >>$(INST_ARCHAUTODIR)extralibs.ld',"\n"); } join('',@m); } sub static_lib_pure_cmd { my ($self, $from) = @_; sprintf <<'MAKE_FRAG', $from; If F$Search("$(MMS$TARGET)").eqs."" Then Library/Object/Create $(MMS$TARGET) Library/Object/Replace $(MMS$TARGET) %s MAKE_FRAG } sub xs_static_lib_is_xs { return 1; } sub extra_clean_files { return qw( *.Map *.Dmp *.Lis *.cpp *.$(DLEXT) *.Opt $(BASEEXT).bso .MM_Tmp cxx_repository ); } sub zipfile_target { my($self) = shift; return <<'MAKE_FRAG'; $(DISTVNAME).zip : distdir $(PREOP) $(ZIP) "$(ZIPFLAGS)" $(MMS$TARGET) [.$(DISTVNAME)...]*.*; $(RM_RF) $(DISTVNAME) $(POSTOP) MAKE_FRAG } sub tarfile_target { my($self) = shift; return <<'MAKE_FRAG'; $(DISTVNAME).tar$(SUFFIX) : distdir $(PREOP) $(TO_UNIX) $(TAR) "$(TARFLAGS)" $(DISTVNAME).tar [.$(DISTVNAME)...] $(RM_RF) $(DISTVNAME) $(COMPRESS) $(DISTVNAME).tar $(POSTOP) MAKE_FRAG } sub shdist_target { my($self) = shift; return <<'MAKE_FRAG'; shdist : distdir $(PREOP) $(SHAR) [.$(DISTVNAME)...]*.*; $(DISTVNAME).share $(RM_RF) $(DISTVNAME) $(POSTOP) MAKE_FRAG } # --- Test and Installation Sections --- sub install { my($self, %attribs) = @_; my(@m); push @m, q[ install :: all pure_install doc_install $(NOECHO) $(NOOP) install_perl :: all pure_perl_install doc_perl_install $(NOECHO) $(NOOP) install_site :: all pure_site_install doc_site_install $(NOECHO) $(NOOP) install_vendor :: all pure_vendor_install doc_vendor_install $(NOECHO) $(NOOP) pure_install :: pure_$(INSTALLDIRS)_install $(NOECHO) $(NOOP) doc_install :: doc_$(INSTALLDIRS)_install $(NOECHO) $(NOOP) pure__install : pure_site_install $(NOECHO) $(ECHO) "INSTALLDIRS not defined, defaulting to INSTALLDIRS=site" doc__install : doc_site_install $(NOECHO) $(ECHO) "INSTALLDIRS not defined, defaulting to INSTALLDIRS=site" # This hack brought to you by DCL's 255-character command line limit pure_perl_install :: ]; push @m, q[ $(NOECHO) $(PERLRUN) "-MFile::Spec" -e "print 'read|'.File::Spec->catfile('$(PERL_ARCHLIB)','auto','$(FULLEXT)','.packlist').'|'" >.MM_tmp $(NOECHO) $(PERLRUN) "-MFile::Spec" -e "print 'write|'.File::Spec->catfile('$(DESTINSTALLARCHLIB)','auto','$(FULLEXT)','.packlist').'|'" >>.MM_tmp ] unless $self->{NO_PACKLIST}; push @m, q[ $(NOECHO) $(ECHO_N) "$(INST_LIB)|$(DESTINSTALLPRIVLIB)|" >>.MM_tmp $(NOECHO) $(ECHO_N) "$(INST_ARCHLIB)|$(DESTINSTALLARCHLIB)|" >>.MM_tmp $(NOECHO) $(ECHO_N) "$(INST_BIN)|$(DESTINSTALLBIN)|" >>.MM_tmp $(NOECHO) $(ECHO_N) "$(INST_SCRIPT)|$(DESTINSTALLSCRIPT)|" >>.MM_tmp $(NOECHO) $(ECHO_N) "$(INST_MAN1DIR) $(DESTINSTALLMAN1DIR) " >>.MM_tmp $(NOECHO) $(ECHO_N) "$(INST_MAN3DIR)|$(DESTINSTALLMAN3DIR)" >>.MM_tmp $(NOECHO) $(MOD_INSTALL) <.MM_tmp $(NOECHO) $(RM_F) .MM_tmp $(NOECHO) $(WARN_IF_OLD_PACKLIST) "].$self->catfile($self->{SITEARCHEXP},'auto',$self->{FULLEXT},'.packlist').q[" # Likewise pure_site_install :: ]; push @m, q[ $(NOECHO) $(PERLRUN) "-MFile::Spec" -e "print 'read|'.File::Spec->catfile('$(SITEARCHEXP)','auto','$(FULLEXT)','.packlist').'|'" >.MM_tmp $(NOECHO) $(PERLRUN) "-MFile::Spec" -e "print 'write|'.File::Spec->catfile('$(DESTINSTALLSITEARCH)','auto','$(FULLEXT)','.packlist').'|'" >>.MM_tmp ] unless $self->{NO_PACKLIST}; push @m, q[ $(NOECHO) $(ECHO_N) "$(INST_LIB)|$(DESTINSTALLSITELIB)|" >>.MM_tmp $(NOECHO) $(ECHO_N) "$(INST_ARCHLIB)|$(DESTINSTALLSITEARCH)|" >>.MM_tmp $(NOECHO) $(ECHO_N) "$(INST_BIN)|$(DESTINSTALLSITEBIN)|" >>.MM_tmp $(NOECHO) $(ECHO_N) "$(INST_SCRIPT)|$(DESTINSTALLSCRIPT)|" >>.MM_tmp $(NOECHO) $(ECHO_N) "$(INST_MAN1DIR)|$(DESTINSTALLSITEMAN1DIR)|" >>.MM_tmp $(NOECHO) $(ECHO_N) "$(INST_MAN3DIR)|$(DESTINSTALLSITEMAN3DIR)" >>.MM_tmp $(NOECHO) $(MOD_INSTALL) <.MM_tmp $(NOECHO) $(RM_F) .MM_tmp $(NOECHO) $(WARN_IF_OLD_PACKLIST) "].$self->catfile($self->{PERL_ARCHLIB},'auto',$self->{FULLEXT},'.packlist').q[" pure_vendor_install :: ]; push @m, q[ $(NOECHO) $(PERLRUN) "-MFile::Spec" -e "print 'read|'.File::Spec->catfile('$(VENDORARCHEXP)','auto','$(FULLEXT)','.packlist').'|'" >.MM_tmp $(NOECHO) $(PERLRUN) "-MFile::Spec" -e "print 'write|'.File::Spec->catfile('$(DESTINSTALLVENDORARCH)','auto','$(FULLEXT)','.packlist').'|'" >>.MM_tmp ] unless $self->{NO_PACKLIST}; push @m, q[ $(NOECHO) $(ECHO_N) "$(INST_LIB)|$(DESTINSTALLVENDORLIB)|" >>.MM_tmp $(NOECHO) $(ECHO_N) "$(INST_ARCHLIB)|$(DESTINSTALLVENDORARCH)|" >>.MM_tmp $(NOECHO) $(ECHO_N) "$(INST_BIN)|$(DESTINSTALLVENDORBIN)|" >>.MM_tmp $(NOECHO) $(ECHO_N) "$(INST_SCRIPT)|$(DESTINSTALLSCRIPT)|" >>.MM_tmp $(NOECHO) $(ECHO_N) "$(INST_MAN1DIR)|$(DESTINSTALLVENDORMAN1DIR)|" >>.MM_tmp $(NOECHO) $(ECHO_N) "$(INST_MAN3DIR)|$(DESTINSTALLVENDORMAN3DIR)" >>.MM_tmp $(NOECHO) $(MOD_INSTALL) <.MM_tmp $(NOECHO) $(RM_F) .MM_tmp ]; push @m, q[ # Ditto doc_perl_install :: $(NOECHO) $(NOOP) # And again doc_site_install :: $(NOECHO) $(NOOP) doc_vendor_install :: $(NOECHO) $(NOOP) ] if $self->{NO_PERLLOCAL}; push @m, q[ # Ditto doc_perl_install :: $(NOECHO) $(ECHO) "Appending installation info to ].$self->catfile($self->{DESTINSTALLARCHLIB}, 'perllocal.pod').q[" $(NOECHO) $(MKPATH) $(DESTINSTALLARCHLIB) $(NOECHO) $(ECHO_N) "installed into|$(INSTALLPRIVLIB)|" >.MM_tmp $(NOECHO) $(ECHO_N) "LINKTYPE|$(LINKTYPE)|VERSION|$(VERSION)|EXE_FILES|$(EXE_FILES) " >>.MM_tmp $(NOECHO) $(DOC_INSTALL) "Module" "$(NAME)" <.MM_tmp >>].$self->catfile($self->{DESTINSTALLARCHLIB},'perllocal.pod').q[ $(NOECHO) $(RM_F) .MM_tmp # And again doc_site_install :: $(NOECHO) $(ECHO) "Appending installation info to ].$self->catfile($self->{DESTINSTALLARCHLIB}, 'perllocal.pod').q[" $(NOECHO) $(MKPATH) $(DESTINSTALLARCHLIB) $(NOECHO) $(ECHO_N) "installed into|$(INSTALLSITELIB)|" >.MM_tmp $(NOECHO) $(ECHO_N) "LINKTYPE|$(LINKTYPE)|VERSION|$(VERSION)|EXE_FILES|$(EXE_FILES) " >>.MM_tmp $(NOECHO) $(DOC_INSTALL) "Module" "$(NAME)" <.MM_tmp >>].$self->catfile($self->{DESTINSTALLARCHLIB},'perllocal.pod').q[ $(NOECHO) $(RM_F) .MM_tmp doc_vendor_install :: $(NOECHO) $(ECHO) "Appending installation info to ].$self->catfile($self->{DESTINSTALLARCHLIB}, 'perllocal.pod').q[" $(NOECHO) $(MKPATH) $(DESTINSTALLARCHLIB) $(NOECHO) $(ECHO_N) "installed into|$(INSTALLVENDORLIB)|" >.MM_tmp $(NOECHO) $(ECHO_N) "LINKTYPE|$(LINKTYPE)|VERSION|$(VERSION)|EXE_FILES|$(EXE_FILES) " >>.MM_tmp $(NOECHO) $(DOC_INSTALL) "Module" "$(NAME)" <.MM_tmp >>].$self->catfile($self->{DESTINSTALLARCHLIB},'perllocal.pod').q[ $(NOECHO) $(RM_F) .MM_tmp ] unless $self->{NO_PERLLOCAL}; push @m, q[ uninstall :: uninstall_from_$(INSTALLDIRS)dirs $(NOECHO) $(NOOP) uninstall_from_perldirs :: $(NOECHO) $(UNINSTALL) ].$self->catfile($self->{PERL_ARCHLIB},'auto',$self->{FULLEXT},'.packlist').q[ uninstall_from_sitedirs :: $(NOECHO) $(UNINSTALL) ].$self->catfile($self->{SITEARCHEXP},'auto',$self->{FULLEXT},'.packlist').q[ uninstall_from_vendordirs :: $(NOECHO) $(UNINSTALL) ].$self->catfile($self->{VENDORARCHEXP},'auto',$self->{FULLEXT},'.packlist').q[ ]; join('',@m); } sub perldepend { my($self) = @_; my(@m); if ($self->{OBJECT}) { # Need to add an object file dependency on the perl headers. # this is very important for XS modules in perl.git development. push @m, $self->_perl_header_files_fragment(""); # empty separator on VMS as its in the $(PERL_INC) } if ($self->{PERL_SRC}) { my(@macros); my($mmsquals) = '$(USEMAKEFILE)[.vms]$(FIRST_MAKEFILE)'; push(@macros,'__AXP__=1') if $Config{'archname'} eq 'VMS_AXP'; push(@macros,'DECC=1') if $Config{'vms_cc_type'} eq 'decc'; push(@macros,'GNUC=1') if $Config{'vms_cc_type'} eq 'gcc'; push(@macros,'SOCKET=1') if $Config{'d_has_sockets'}; push(@macros,qq["CC=$Config{'cc'}"]) if $Config{'cc'} =~ m!/!; $mmsquals .= '$(USEMACROS)' . join(',',@macros) . '$(MACROEND)' if @macros; push(@m,q[ # Check for unpropagated config.sh changes. Should never happen. # We do NOT just update config.h because that is not sufficient. # An out of date config.h is not fatal but complains loudly! $(PERL_INC)config.h : $(PERL_SRC)config.sh $(NOOP) $(PERL_ARCHLIB)Config.pm : $(PERL_SRC)config.sh $(NOECHO) Write Sys$Error "$(PERL_ARCHLIB)Config.pm may be out of date with config.h or genconfig.pl" olddef = F$Environment("Default") Set Default $(PERL_SRC) $(MMS)],$mmsquals,); if ($self->{PERL_ARCHLIB} =~ m|\[-| && $self->{PERL_SRC} =~ m|(\[-+)|) { my($prefix,$target) = ($1,$self->fixpath('$(PERL_ARCHLIB)Config.pm',0)); $target =~ s/\Q$prefix/[/; push(@m," $target"); } else { push(@m,' $(MMS$TARGET)'); } push(@m,q[ Set Default 'olddef' ]); } push(@m, join(" ", map($self->fixpath($_,0),sort values %{$self->{XS}}))." : \$(XSUBPPDEPS)\n") if %{$self->{XS}}; join('',@m); } our %olbs; # needs to be localized sub makeaperl { my($self, %attribs) = @_; my($makefilename, $searchdirs, $static, $extra, $perlinc, $target, $tmpdir, $libperl) = @attribs{qw(MAKE DIRS STAT EXTRA INCL TARGET TMP LIBPERL)}; my(@m); push @m, " # --- MakeMaker makeaperl section --- MAP_TARGET = $target "; return join '', @m if $self->{PARENT}; my($dir) = join ":", @{$self->{DIR}}; unless ($self->{MAKEAPERL}) { push @m, q{ $(MAKE_APERL_FILE) : $(FIRST_MAKEFILE) $(NOECHO) $(ECHO) "Writing ""$(MMS$TARGET)"" for this $(MAP_TARGET)" $(NOECHO) $(PERLRUNINST) \ Makefile.PL DIR=}, $dir, q{ \ FIRST_MAKEFILE=$(MAKE_APERL_FILE) LINKTYPE=static \ MAKEAPERL=1 NORECURS=1 }; push @m, map(q[ \\\n\t\t"$_"], @ARGV),q{ $(MAP_TARGET) :: $(MAKE_APERL_FILE) $(MAKE)$(USEMAKEFILE)$(MAKE_APERL_FILE) static $(MMS$TARGET) }; push @m, "\n"; return join '', @m; } my($linkcmd,@optlibs,@staticpkgs,$extralist,$targdir,$libperldir,%libseen); local($_); # The front matter of the linkcommand... $linkcmd = join ' ', $Config{'ld'}, grep($_, @Config{qw(large split ldflags ccdlflags)}); $linkcmd =~ s/\s+/ /g; # Which *.olb files could we make use of... local(%olbs); # XXX can this be lexical? $olbs{$self->{INST_ARCHAUTODIR}} = "$self->{BASEEXT}\$(LIB_EXT)"; require File::Find; File::Find::find(sub { return unless m/\Q$self->{LIB_EXT}\E$/; return if m/^libperl/; if( exists $self->{INCLUDE_EXT} ){ my $found = 0; (my $xx = $File::Find::name) =~ s,.*?/auto/,,; $xx =~ s,/?$_,,; $xx =~ s,/,::,g; # Throw away anything not explicitly marked for inclusion. # DynaLoader is implied. foreach my $incl ((@{$self->{INCLUDE_EXT}},'DynaLoader')){ if( $xx eq $incl ){ $found++; last; } } return unless $found; } elsif( exists $self->{EXCLUDE_EXT} ){ (my $xx = $File::Find::name) =~ s,.*?/auto/,,; $xx =~ s,/?$_,,; $xx =~ s,/,::,g; # Throw away anything explicitly marked for exclusion foreach my $excl (@{$self->{EXCLUDE_EXT}}){ return if( $xx eq $excl ); } } $olbs{$ENV{DEFAULT}} = $_; }, grep( -d $_, @{$searchdirs || []})); # We trust that what has been handed in as argument will be buildable $static = [] unless $static; @olbs{@{$static}} = (1) x @{$static}; $extra = [] unless $extra && ref $extra eq 'ARRAY'; # Sort the object libraries in inverse order of # filespec length to try to insure that dependent extensions # will appear before their parents, so the linker will # search the parent library to resolve references. # (e.g. Intuit::DWIM will precede Intuit, so unresolved # references from [.intuit.dwim]dwim.obj can be found # in [.intuit]intuit.olb). for (sort { length($a) <=> length($b) || $a cmp $b } keys %olbs) { next unless $olbs{$_} =~ /\Q$self->{LIB_EXT}\E$/; my($dir) = $self->fixpath($_,1); my($extralibs) = $dir . "extralibs.ld"; my($extopt) = $dir . $olbs{$_}; $extopt =~ s/$self->{LIB_EXT}$/.opt/; push @optlibs, "$dir$olbs{$_}"; # Get external libraries this extension will need if (-f $extralibs ) { my %seenthis; open my $list, "<", $extralibs or warn $!,next; while (<$list>) { chomp; # Include a library in the link only once, unless it's mentioned # multiple times within a single extension's options file, in which # case we assume the builder needed to search it again later in the # link. my $skip = exists($libseen{$_}) && !exists($seenthis{$_}); $libseen{$_}++; $seenthis{$_}++; next if $skip; push @$extra,$_; } } # Get full name of extension for ExtUtils::Miniperl if (-f $extopt) { open my $opt, '<', $extopt or die $!; while (<$opt>) { next unless /(?:UNIVERSAL|VECTOR)=boot_([\w_]+)/; my $pkg = $1; $pkg =~ s#__*#::#g; push @staticpkgs,$pkg; } } } # Place all of the external libraries after all of the Perl extension # libraries in the final link, in order to maximize the opportunity # for XS code from multiple extensions to resolve symbols against the # same external library while only including that library once. push @optlibs, @$extra; $target = "Perl$Config{'exe_ext'}" unless $target; my $shrtarget; ($shrtarget,$targdir) = fileparse($target); $shrtarget =~ s/^([^.]*)/$1Shr/; $shrtarget = $targdir . $shrtarget; $target = "Perlshr.$Config{'dlext'}" unless $target; $tmpdir = "[]" unless $tmpdir; $tmpdir = $self->fixpath($tmpdir,1); if (@optlibs) { $extralist = join(' ',@optlibs); } else { $extralist = ''; } # Let ExtUtils::Liblist find the necessary libs for us (but skip PerlShr) # that's what we're building here). push @optlibs, grep { !/PerlShr/i } split ' ', +($self->ext())[2]; if ($libperl) { unless (-f $libperl || -f ($libperl = $self->catfile($Config{'installarchlib'},'CORE',$libperl))) { print "Warning: $libperl not found\n"; undef $libperl; } } unless ($libperl) { if (defined $self->{PERL_SRC}) { $libperl = $self->catfile($self->{PERL_SRC},"libperl$self->{LIB_EXT}"); } elsif (-f ($libperl = $self->catfile($Config{'installarchlib'},'CORE',"libperl$self->{LIB_EXT}")) ) { } else { print "Warning: $libperl not found If you're going to build a static perl binary, make sure perl is installed otherwise ignore this warning\n"; } } $libperldir = $self->fixpath((fileparse($libperl))[1],1); push @m, ' # Fill in the target you want to produce if it\'s not perl MAP_TARGET = ',$self->fixpath($target,0),' MAP_SHRTARGET = ',$self->fixpath($shrtarget,0)," MAP_LINKCMD = $linkcmd MAP_PERLINC = ", $perlinc ? map('"$_" ',@{$perlinc}) : ''," MAP_EXTRA = $extralist MAP_LIBPERL = ",$self->fixpath($libperl,0),' '; push @m,"\n${tmpdir}Makeaperl.Opt : \$(MAP_EXTRA)\n"; foreach (@optlibs) { push @m,' $(NOECHO) $(PERL) -e "print q{',$_,'}" >>$(MMS$TARGET)',"\n"; } push @m,"\n${tmpdir}PerlShr.Opt :\n\t"; push @m,'$(NOECHO) $(PERL) -e "print q{$(MAP_SHRTARGET)}" >$(MMS$TARGET)',"\n"; push @m,' $(MAP_SHRTARGET) : $(MAP_LIBPERL) Makeaperl.Opt ',"${libperldir}Perlshr_Attr.Opt",' $(MAP_LINKCMD)/Shareable=$(MMS$TARGET) $(MAP_LIBPERL), Makeaperl.Opt/Option ',"${libperldir}Perlshr_Attr.Opt/Option",' $(MAP_TARGET) : $(MAP_SHRTARGET) ',"${tmpdir}perlmain\$(OBJ_EXT) ${tmpdir}PerlShr.Opt",' $(MAP_LINKCMD) ',"${tmpdir}perlmain\$(OBJ_EXT)",', PerlShr.Opt/Option $(NOECHO) $(ECHO) "To install the new ""$(MAP_TARGET)"" binary, say" $(NOECHO) $(ECHO) " $(MAKE)$(USEMAKEFILE)$(FIRST_MAKEFILE) inst_perl $(USEMACROS)MAP_TARGET=$(MAP_TARGET)$(ENDMACRO)" $(NOECHO) $(ECHO) "To remove the intermediate files, say $(NOECHO) $(ECHO) " $(MAKE)$(USEMAKEFILE)$(FIRST_MAKEFILE) map_clean" '; push @m,"\n${tmpdir}perlmain.c : \$(FIRST_MAKEFILE)\n\t\$(NOECHO) \$(PERL) -e 1 >${tmpdir}Writemain.tmp\n"; push @m, "# More from the 255-char line length limit\n"; foreach (@staticpkgs) { push @m,' $(NOECHO) $(PERL) -e "print q{',$_,qq[}" >>${tmpdir}Writemain.tmp\n]; } push @m, sprintf <<'MAKE_FRAG', $tmpdir, $tmpdir; $(NOECHO) $(PERL) $(MAP_PERLINC) -ane "use ExtUtils::Miniperl; writemain(@F)" %sWritemain.tmp >$(MMS$TARGET) $(NOECHO) $(RM_F) %sWritemain.tmp MAKE_FRAG push @m, q[ # Still more from the 255-char line length limit doc_inst_perl : $(NOECHO) $(MKPATH) $(DESTINSTALLARCHLIB) $(NOECHO) $(ECHO) "Perl binary $(MAP_TARGET)|" >.MM_tmp $(NOECHO) $(ECHO) "MAP_STATIC|$(MAP_STATIC)|" >>.MM_tmp $(NOECHO) $(PERL) -pl040 -e " " ].$self->catfile('$(INST_ARCHAUTODIR)','extralibs.all'),q[ >>.MM_tmp $(NOECHO) $(ECHO) -e "MAP_LIBPERL|$(MAP_LIBPERL)|" >>.MM_tmp $(NOECHO) $(DOC_INSTALL) <.MM_tmp >>].$self->catfile('$(DESTINSTALLARCHLIB)','perllocal.pod').q[ $(NOECHO) $(RM_F) .MM_tmp ]; push @m, " inst_perl : pure_inst_perl doc_inst_perl \$(NOECHO) \$(NOOP) pure_inst_perl : \$(MAP_TARGET) $self->{CP} \$(MAP_SHRTARGET) ",$self->fixpath($Config{'installbin'},1)," $self->{CP} \$(MAP_TARGET) ",$self->fixpath($Config{'installbin'},1)," clean :: map_clean \$(NOECHO) \$(NOOP) map_clean : \$(RM_F) ${tmpdir}perlmain\$(OBJ_EXT) ${tmpdir}perlmain.c \$(FIRST_MAKEFILE) \$(RM_F) ${tmpdir}Makeaperl.Opt ${tmpdir}PerlShr.Opt \$(MAP_TARGET) "; join '', @m; } # --- Output postprocessing section --- sub maketext_filter { my($self, $text) = @_; $text =~ s/^([^\s:=]+)(:+\s)/$1 $2/mg; return $text; } sub prefixify { my($self, $var, $sprefix, $rprefix, $default) = @_; # Translate $(PERLPREFIX) to a real path. $rprefix = $self->eliminate_macros($rprefix); $rprefix = vmspath($rprefix) if $rprefix; $sprefix = vmspath($sprefix) if $sprefix; $default = vmsify($default) unless $default =~ /\[.*\]/; (my $var_no_install = $var) =~ s/^install//; my $path = $self->{uc $var} || $ExtUtils::MM_Unix::Config_Override{lc $var} || $Config{lc $var} || $Config{lc $var_no_install}; if( !$path ) { warn " no Config found for $var.\n" if $Verbose >= 2; $path = $self->_prefixify_default($rprefix, $default); } elsif( !$self->{ARGS}{PREFIX} || !$self->file_name_is_absolute($path) ) { # do nothing if there's no prefix or if its relative } elsif( $sprefix eq $rprefix ) { warn " no new prefix.\n" if $Verbose >= 2; } else { warn " prefixify $var => $path\n" if $Verbose >= 2; warn " from $sprefix to $rprefix\n" if $Verbose >= 2; my($path_vol, $path_dirs) = $self->splitpath( $path ); if( $path_vol eq $Config{vms_prefix}.':' ) { warn " $Config{vms_prefix}: seen\n" if $Verbose >= 2; $path_dirs =~ s{^\[}{\[.} unless $path_dirs =~ m{^\[\.}; $path = $self->_catprefix($rprefix, $path_dirs); } else { $path = $self->_prefixify_default($rprefix, $default); } } print " now $path\n" if $Verbose >= 2; return $self->{uc $var} = $path; } sub _prefixify_default { my($self, $rprefix, $default) = @_; warn " cannot prefix, using default.\n" if $Verbose >= 2; if( !$default ) { warn "No default!\n" if $Verbose >= 1; return; } if( !$rprefix ) { warn "No replacement prefix!\n" if $Verbose >= 1; return ''; } return $self->_catprefix($rprefix, $default); } sub _catprefix { my($self, $rprefix, $default) = @_; my($rvol, $rdirs) = $self->splitpath($rprefix); if( $rvol ) { return $self->catpath($rvol, $self->catdir($rdirs, $default), '' ) } else { return $self->catdir($rdirs, $default); } } sub cd { my($self, $dir, @cmds) = @_; $dir = vmspath($dir); my $cmd = join "\n\t", map "$_", @cmds; # No leading tab makes it look right when embedded my $make_frag = sprintf <<'MAKE_FRAG', $dir, $cmd; startdir = F$Environment("Default") Set Default %s %s Set Default 'startdir' MAKE_FRAG # No trailing newline makes this easier to embed chomp $make_frag; return $make_frag; } sub oneliner { my($self, $cmd, $switches) = @_; $switches = [] unless defined $switches; # Strip leading and trailing newlines $cmd =~ s{^\n+}{}; $cmd =~ s{\n+$}{}; my @cmds = split /\n/, $cmd; $cmd = join " \n\t -e ", map $self->quote_literal($_), @cmds; $cmd = $self->escape_newlines($cmd); # Switches must be quoted else they will be lowercased. $switches = join ' ', map { qq{"$_"} } @$switches; return qq{\$(ABSPERLRUN) $switches -e $cmd "--"}; } sub echo { my($self, $text, $file, $opts) = @_; # Compatibility with old options if( !ref $opts ) { my $append = $opts; $opts = { append => $append || 0 }; } my $opencmd = $opts->{append} ? 'Open/Append' : 'Open/Write'; $opts->{allow_variables} = 0 unless defined $opts->{allow_variables}; my $ql_opts = { allow_variables => $opts->{allow_variables} }; my @cmds = ("\$(NOECHO) $opencmd MMECHOFILE $file "); push @cmds, map { '$(NOECHO) Write MMECHOFILE '.$self->quote_literal($_, $ql_opts) } split /\n/, $text; push @cmds, '$(NOECHO) Close MMECHOFILE'; return @cmds; } sub quote_literal { my($self, $text, $opts) = @_; $opts->{allow_variables} = 1 unless defined $opts->{allow_variables}; # I believe this is all we should need. $text =~ s{"}{""}g; $text = $opts->{allow_variables} ? $self->escape_dollarsigns($text) : $self->escape_all_dollarsigns($text); return qq{"$text"}; } sub escape_dollarsigns { my($self, $text) = @_; # Quote dollar signs which are not starting a variable $text =~ s{\$ (?!\() }{"\$"}gx; return $text; } sub escape_all_dollarsigns { my($self, $text) = @_; # Quote dollar signs $text =~ s{\$}{"\$\"}gx; return $text; } sub escape_newlines { my($self, $text) = @_; $text =~ s{\n}{-\n}g; return $text; } sub max_exec_len { my $self = shift; return $self->{_MAX_EXEC_LEN} ||= 256; } sub init_linker { my $self = shift; $self->{EXPORT_LIST} ||= '$(BASEEXT).opt'; my $shr = $Config{dbgprefix} . 'PERLSHR'; if ($self->{PERL_SRC}) { $self->{PERL_ARCHIVE} ||= $self->catfile($self->{PERL_SRC}, "$shr.$Config{'dlext'}"); } else { $self->{PERL_ARCHIVE} ||= $ENV{$shr} ? $ENV{$shr} : "Sys\$Share:$shr.$Config{'dlext'}"; } $self->{PERL_ARCHIVEDEP} ||= ''; $self->{PERL_ARCHIVE_AFTER} ||= ''; } sub catdir { my $self = shift; # Process the macros on VMS MMS/MMK my @args = map { m{\$\(} ? $self->eliminate_macros($_) : $_ } @_; my $dir = $self->SUPER::catdir(@args); # Fix up the directory and force it to VMS format. $dir = $self->fixpath($dir, 1); return $dir; } sub catfile { my $self = shift; # Process the macros on VMS MMS/MMK my @args = map { m{\$\(} ? $self->eliminate_macros($_) : $_ } @_; my $file = $self->SUPER::catfile(@args); $file = vmsify($file); return $file } sub eliminate_macros { my($self,$path) = @_; return '' unless $path; $self = {} unless ref $self; my($npath) = unixify($path); # sometimes unixify will return a string with an off-by-one trailing null $npath =~ s{\0$}{}; my($complex) = 0; my($head,$macro,$tail); # perform m##g in scalar context so it acts as an iterator while ($npath =~ m#(.*?)\$\((\S+?)\)(.*)#gs) { if (defined $self->{$2}) { ($head,$macro,$tail) = ($1,$2,$3); if (ref $self->{$macro}) { if (ref $self->{$macro} eq 'ARRAY') { $macro = join ' ', @{$self->{$macro}}; } else { print "Note: can't expand macro \$($macro) containing ",ref($self->{$macro}), "\n\t(using MMK-specific deferred substitutuon; MMS will break)\n"; $macro = "\cB$macro\cB"; $complex = 1; } } else { $macro = $self->{$macro}; # Don't unixify if there is unescaped whitespace $macro = unixify($macro) unless ($macro =~ /(?\]]#) { if ($force_path or $path =~ /(?:DIR\)|\])\Z(?!\n)/) { $fixedpath = vmspath($self->eliminate_macros($path)); } else { $fixedpath = vmsify($self->eliminate_macros($path)); } } elsif ((($prefix,$name) = ($path =~ m#^\$\(([^\)]+)\)(.+)#s)) && $self->{$prefix}) { my($vmspre) = $self->eliminate_macros("\$($prefix)"); # is it a dir or just a name? $vmspre = ($vmspre =~ m|/| or $prefix =~ /DIR\Z(?!\n)/) ? vmspath($vmspre) : ''; $fixedpath = ($vmspre ? $vmspre : $self->{$prefix}) . $name; $fixedpath = vmspath($fixedpath) if $force_path; } else { $fixedpath = $path; $fixedpath = vmspath($fixedpath) if $force_path; } # No hints, so we try to guess if (!defined($force_path) and $fixedpath !~ /[:>(.\]]/) { $fixedpath = vmspath($fixedpath) if -d $fixedpath; } # Trim off root dirname if it's had other dirs inserted in front of it. $fixedpath =~ s/\.000000([\]>])/$1/; # Special case for VMS absolute directory specs: these will have had device # prepended during trip through Unix syntax in eliminate_macros(), since # Unix syntax has no way to express "absolute from the top of this device's # directory tree". if ($path =~ /^[\[>][^.\-]/) { $fixedpath =~ s/^[^\[<]+//; } return $fixedpath; } sub os_flavor { return('VMS'); } sub is_make_type { my($self, $type) = @_; return 0; } sub make_type { "$Config{make}-style"; } 1; # Avoid version control files. \bRCS\b \bCVS\b \bSCCS\b ,v$ \B\.svn\b \B\.git\b \B\.gitignore\b \b_darcs\b \B\.cvsignore$ # Avoid VMS specific MakeMaker generated files \bDescrip.MMS$ \bDESCRIP.MMS$ \bdescrip.mms$ # Avoid Makemaker generated and utility files. \bMANIFEST\.bak \bMakefile$ \bblib/ \bMakeMaker-\d \bpm_to_blib\.ts$ \bpm_to_blib$ \bblibdirs\.ts$ # 6.18 through 6.25 generated this \b_eumm/ # 7.05_05 and above # Avoid Module::Build generated and utility files. \bBuild$ \b_build/ \bBuild.bat$ \bBuild.COM$ \bBUILD.COM$ \bbuild.com$ # and Module::Build::Tiny generated files \b_build_params$ # Avoid temp and backup files. ~$ \.old$ \#$ \b\.# \.bak$ \.tmp$ \.# \.rej$ \..*\.sw.?$ # Avoid OS-specific files/dirs # Mac OSX metadata \B\.DS_Store # Mac OSX SMB mount metadata files \B\._ # Avoid Devel::Cover and Devel::CoverX::Covered files. \bcover_db\b \bcovered\b # Avoid prove files \B\.prove$ # Avoid MYMETA files ^MYMETA\. package ExtUtils::MM_Unix; require 5.006; use strict; use Carp; use ExtUtils::MakeMaker::Config; use File::Basename qw(basename dirname); our %Config_Override; use ExtUtils::MakeMaker qw($Verbose neatvalue _sprintf562); # If we make $VERSION an our variable parse_version() breaks use vars qw($VERSION); $VERSION = '7.34'; $VERSION = eval $VERSION; ## no critic [BuiltinFunctions::ProhibitStringyEval] require ExtUtils::MM_Any; our @ISA = qw(ExtUtils::MM_Any); my %Is; BEGIN { $Is{OS2} = $^O eq 'os2'; $Is{Win32} = $^O eq 'MSWin32' || $Config{osname} eq 'NetWare'; $Is{Dos} = $^O eq 'dos'; $Is{VMS} = $^O eq 'VMS'; $Is{OSF} = $^O eq 'dec_osf'; $Is{IRIX} = $^O eq 'irix'; $Is{NetBSD} = $^O eq 'netbsd'; $Is{Interix} = $^O eq 'interix'; $Is{SunOS4} = $^O eq 'sunos'; $Is{Solaris} = $^O eq 'solaris'; $Is{SunOS} = $Is{SunOS4} || $Is{Solaris}; $Is{BSD} = ($^O =~ /^(?:free|net|open)bsd$/ or grep( $^O eq $_, qw(bsdos interix dragonfly) ) ); $Is{Android} = $^O =~ /android/; } BEGIN { if( $Is{VMS} ) { # For things like vmsify() require VMS::Filespec; VMS::Filespec->import; } } # So we don't have to keep calling the methods over and over again, # we have these globals to cache the values. Faster and shrtr. my $Curdir = __PACKAGE__->curdir; my $Updir = __PACKAGE__->updir; sub os_flavor { return('Unix'); } sub c_o { # --- Translation Sections --- my($self) = shift; return '' unless $self->needs_linking(); my(@m); my $command = '$(CCCMD)'; my $flags = '$(CCCDLFLAGS) "-I$(PERL_INC)" $(PASTHRU_DEFINE) $(DEFINE)'; if (my $cpp = $Config{cpprun}) { my $cpp_cmd = $self->const_cccmd; $cpp_cmd =~ s/^CCCMD\s*=\s*\$\(CC\)/$cpp/; push @m, qq{ .c.i: $cpp_cmd $flags \$*.c > \$*.i }; } my $m_o = $self->{XSMULTI} ? $self->xs_obj_opt('$*.s') : ''; push @m, sprintf <<'EOF', $command, $flags, $m_o; .c.s : %s -S %s $*.c %s EOF my @exts = qw(c cpp cxx cc); push @exts, 'C' if !$Is{OS2} and !$Is{Win32} and !$Is{Dos}; #Case-specific $m_o = $self->{XSMULTI} ? $self->xs_obj_opt('$*$(OBJ_EXT)') : ''; for my $ext (@exts) { push @m, "\n.$ext\$(OBJ_EXT) :\n\t$command $flags \$*.$ext" . ( $m_o ? " $m_o" : '' ) . "\n"; } return join "", @m; } sub xs_obj_opt { my ($self, $output_file) = @_; "-o $output_file"; } #' sub cflags { my($self,$libperl)=@_; return $self->{CFLAGS} if $self->{CFLAGS}; return '' unless $self->needs_linking(); my($prog, $uc, $perltype, %cflags); $libperl ||= $self->{LIBPERL_A} || "libperl$self->{LIB_EXT}" ; $libperl =~ s/\.\$\(A\)$/$self->{LIB_EXT}/; @cflags{qw(cc ccflags optimize shellflags)} = @Config{qw(cc ccflags optimize shellflags)}; # Perl 5.21.4 adds the (gcc) warning (-Wall ...) and std (-std=c89) # flags to the %Config, and the modules in the core should be built # with the warning flags, but NOT the -std=c89 flags (the latter # would break using any system header files that are strict C99). my @ccextraflags = qw(ccwarnflags); if ($ENV{PERL_CORE}) { for my $x (@ccextraflags) { if (exists $Config{$x}) { $cflags{$x} = $Config{$x}; } } } my($optdebug) = ""; $cflags{shellflags} ||= ''; my(%map) = ( D => '-DDEBUGGING', E => '-DEMBED', DE => '-DDEBUGGING -DEMBED', M => '-DEMBED -DMULTIPLICITY', DM => '-DDEBUGGING -DEMBED -DMULTIPLICITY', ); if ($libperl =~ /libperl(\w*)\Q$self->{LIB_EXT}/){ $uc = uc($1); } else { $uc = ""; # avoid warning } $perltype = $map{$uc} ? $map{$uc} : ""; if ($uc =~ /^D/) { $optdebug = "-g"; } my($name); ( $name = $self->{NAME} . "_cflags" ) =~ s/:/_/g ; if ($prog = $Config{$name}) { # Expand hints for this extension via the shell print "Processing $name hint:\n" if $Verbose; my(@o)=`cc=\"$cflags{cc}\" ccflags=\"$cflags{ccflags}\" optimize=\"$cflags{optimize}\" perltype=\"$cflags{perltype}\" optdebug=\"$cflags{optdebug}\" eval '$prog' echo cc=\$cc echo ccflags=\$ccflags echo optimize=\$optimize echo perltype=\$perltype echo optdebug=\$optdebug `; foreach my $line (@o){ chomp $line; if ($line =~ /(.*?)=\s*(.*)\s*$/){ $cflags{$1} = $2; print " $1 = $2\n" if $Verbose; } else { print "Unrecognised result from hint: '$line'\n"; } } } if ($optdebug) { $cflags{optimize} = $optdebug; } for (qw(ccflags optimize perltype)) { $cflags{$_} ||= ''; $cflags{$_} =~ s/^\s+//; $cflags{$_} =~ s/\s+/ /g; $cflags{$_} =~ s/\s+$//; $self->{uc $_} ||= $cflags{$_}; } if ($self->{POLLUTE}) { $self->{CCFLAGS} .= ' -DPERL_POLLUTE '; } for my $x (@ccextraflags) { next unless exists $cflags{$x}; $self->{CCFLAGS} .= $cflags{$x} =~ m!^\s! ? $cflags{$x} : ' ' . $cflags{$x}; } my $pollute = ''; if ($Config{usemymalloc} and not $Config{bincompat5005} and not $Config{ccflags} =~ /-DPERL_POLLUTE_MALLOC\b/ and $self->{PERL_MALLOC_OK}) { $pollute = '$(PERL_MALLOC_DEF)'; } return $self->{CFLAGS} = qq{ CCFLAGS = $self->{CCFLAGS} OPTIMIZE = $self->{OPTIMIZE} PERLTYPE = $self->{PERLTYPE} MPOLLUTE = $pollute }; } sub const_cccmd { my($self,$libperl)=@_; return $self->{CONST_CCCMD} if $self->{CONST_CCCMD}; return '' unless $self->needs_linking(); return $self->{CONST_CCCMD} = q{CCCMD = $(CC) -c $(PASTHRU_INC) $(INC) \\ $(CCFLAGS) $(OPTIMIZE) \\ $(PERLTYPE) $(MPOLLUTE) $(DEFINE_VERSION) \\ $(XS_DEFINE_VERSION)}; } sub const_config { # --- Constants Sections --- my($self) = shift; my @m = $self->specify_shell(); # Usually returns empty string push @m, <<"END"; # These definitions are from config.sh (via $INC{'Config.pm'}). # They may have been overridden via Makefile.PL or on the command line. END my(%once_only); foreach my $key (@{$self->{CONFIG}}){ # SITE*EXP macros are defined in &constants; avoid duplicates here next if $once_only{$key}; push @m, uc($key) , ' = ' , $self->{uc $key}, "\n"; $once_only{$key} = 1; } join('', @m); } sub const_loadlibs { my($self) = shift; return "" unless $self->needs_linking; my @m; push @m, qq{ # $self->{NAME} might depend on some other libraries: # See ExtUtils::Liblist for details # }; for my $tmp (qw/ EXTRALIBS LDLOADLIBS BSLOADLIBS /) { next unless defined $self->{$tmp}; push @m, "$tmp = $self->{$tmp}\n"; } # don't set LD_RUN_PATH if empty for my $tmp (qw/ LD_RUN_PATH /) { next unless $self->{$tmp}; push @m, "$tmp = $self->{$tmp}\n"; } return join "", @m; } sub constants { my($self) = @_; my @m = (); $self->{DFSEP} = '$(DIRFILESEP)'; # alias for internal use for my $macro (qw( AR_STATIC_ARGS DIRFILESEP DFSEP NAME NAME_SYM VERSION VERSION_MACRO VERSION_SYM DEFINE_VERSION XS_VERSION XS_VERSION_MACRO XS_DEFINE_VERSION INST_ARCHLIB INST_SCRIPT INST_BIN INST_LIB INST_MAN1DIR INST_MAN3DIR MAN1EXT MAN3EXT INSTALLDIRS INSTALL_BASE DESTDIR PREFIX PERLPREFIX SITEPREFIX VENDORPREFIX ), (map { ("INSTALL".$_, "DESTINSTALL".$_) } $self->installvars), qw( PERL_LIB PERL_ARCHLIB PERL_ARCHLIBDEP LIBPERL_A MYEXTLIB FIRST_MAKEFILE MAKEFILE_OLD MAKE_APERL_FILE PERLMAINCC PERL_SRC PERL_INC PERL_INCDEP PERL FULLPERL ABSPERL PERLRUN FULLPERLRUN ABSPERLRUN PERLRUNINST FULLPERLRUNINST ABSPERLRUNINST PERL_CORE PERM_DIR PERM_RW PERM_RWX ) ) { next unless defined $self->{$macro}; # pathnames can have sharp signs in them; escape them so # make doesn't think it is a comment-start character. $self->{$macro} =~ s/#/\\#/g; $self->{$macro} = $self->quote_dep($self->{$macro}) if $ExtUtils::MakeMaker::macro_dep{$macro}; push @m, "$macro = $self->{$macro}\n"; } push @m, qq{ MAKEMAKER = $self->{MAKEMAKER} MM_VERSION = $self->{MM_VERSION} MM_REVISION = $self->{MM_REVISION} }; push @m, q{ # FULLEXT = Pathname for extension directory (eg Foo/Bar/Oracle). # BASEEXT = Basename part of FULLEXT. May be just equal FULLEXT. (eg Oracle) # PARENT_NAME = NAME without BASEEXT and no trailing :: (eg Foo::Bar) # DLBASE = Basename part of dynamic library. May be just equal BASEEXT. }; for my $macro (qw/ MAKE FULLEXT BASEEXT PARENT_NAME DLBASE VERSION_FROM INC DEFINE OBJECT LDFROM LINKTYPE BOOTDEP / ) { next unless defined $self->{$macro}; push @m, "$macro = $self->{$macro}\n"; } push @m, " # Handy lists of source code files: XS_FILES = ".$self->wraplist(sort keys %{$self->{XS}})." C_FILES = ".$self->wraplist(sort @{$self->{C}})." O_FILES = ".$self->wraplist(sort @{$self->{O_FILES}})." H_FILES = ".$self->wraplist(sort @{$self->{H}})." MAN1PODS = ".$self->wraplist(sort keys %{$self->{MAN1PODS}})." MAN3PODS = ".$self->wraplist(sort keys %{$self->{MAN3PODS}})." "; push @m, q{ # Where is the Config information that we are using/depend on CONFIGDEP = $(PERL_ARCHLIBDEP)$(DFSEP)Config.pm $(PERL_INCDEP)$(DFSEP)config.h } if -e $self->catfile( $self->{PERL_INC}, 'config.h' ); push @m, qq{ # Where to build things INST_LIBDIR = $self->{INST_LIBDIR} INST_ARCHLIBDIR = $self->{INST_ARCHLIBDIR} INST_AUTODIR = $self->{INST_AUTODIR} INST_ARCHAUTODIR = $self->{INST_ARCHAUTODIR} INST_STATIC = $self->{INST_STATIC} INST_DYNAMIC = $self->{INST_DYNAMIC} INST_BOOT = $self->{INST_BOOT} }; push @m, qq{ # Extra linker info EXPORT_LIST = $self->{EXPORT_LIST} PERL_ARCHIVE = $self->{PERL_ARCHIVE} PERL_ARCHIVEDEP = $self->{PERL_ARCHIVEDEP} PERL_ARCHIVE_AFTER = $self->{PERL_ARCHIVE_AFTER} }; push @m, " TO_INST_PM = ".$self->wraplist(map $self->quote_dep($_), sort keys %{$self->{PM}})."\n"; join('',@m); } sub depend { my($self,%attribs) = @_; my(@m,$key,$val); for my $key (sort keys %attribs){ my $val = $attribs{$key}; next unless defined $key and defined $val; push @m, "$key : $val\n"; } join "", @m; } sub init_DEST { my $self = shift; # Initialize DESTDIR $self->{DESTDIR} ||= ''; # Make DEST variables. foreach my $var ($self->installvars) { my $destvar = 'DESTINSTALL'.$var; $self->{$destvar} ||= '$(DESTDIR)$(INSTALL'.$var.')'; } } sub init_dist { my $self = shift; $self->{TAR} ||= 'tar'; $self->{TARFLAGS} ||= 'cvf'; $self->{ZIP} ||= 'zip'; $self->{ZIPFLAGS} ||= '-r'; $self->{COMPRESS} ||= 'gzip --best'; $self->{SUFFIX} ||= '.gz'; $self->{SHAR} ||= 'shar'; $self->{PREOP} ||= '$(NOECHO) $(NOOP)'; # eg update MANIFEST $self->{POSTOP} ||= '$(NOECHO) $(NOOP)'; # eg remove the distdir $self->{TO_UNIX} ||= '$(NOECHO) $(NOOP)'; $self->{CI} ||= 'ci -u'; $self->{RCS_LABEL}||= 'rcs -Nv$(VERSION_SYM): -q'; $self->{DIST_CP} ||= 'best'; $self->{DIST_DEFAULT} ||= 'tardist'; ($self->{DISTNAME} = $self->{NAME}) =~ s{::}{-}g unless $self->{DISTNAME}; $self->{DISTVNAME} ||= $self->{DISTNAME}.'-'.$self->{VERSION}; } sub dist { my($self, %attribs) = @_; my $make = ''; if ( $attribs{SUFFIX} && $attribs{SUFFIX} !~ m!^\.! ) { $attribs{SUFFIX} = '.' . $attribs{SUFFIX}; } foreach my $key (qw( TAR TARFLAGS ZIP ZIPFLAGS COMPRESS SUFFIX SHAR PREOP POSTOP TO_UNIX CI RCS_LABEL DIST_CP DIST_DEFAULT DISTNAME DISTVNAME )) { my $value = $attribs{$key} || $self->{$key}; $make .= "$key = $value\n"; } return $make; } sub dist_basics { my($self) = shift; return <<'MAKE_FRAG'; distclean :: realclean distcheck $(NOECHO) $(NOOP) distcheck : $(PERLRUN) "-MExtUtils::Manifest=fullcheck" -e fullcheck skipcheck : $(PERLRUN) "-MExtUtils::Manifest=skipcheck" -e skipcheck manifest : $(PERLRUN) "-MExtUtils::Manifest=mkmanifest" -e mkmanifest veryclean : realclean $(RM_F) *~ */*~ *.orig */*.orig *.bak */*.bak *.old */*.old MAKE_FRAG } sub dist_ci { my($self) = shift; return sprintf "ci :\n\t%s\n", $self->oneliner(<<'EOF', [qw(-MExtUtils::Manifest=maniread)]); @all = sort keys %{ maniread() }; print(qq{Executing $(CI) @all\n}); system(qq{$(CI) @all}) == 0 or die $!; print(qq{Executing $(RCS_LABEL) ...\n}); system(qq{$(RCS_LABEL) @all}) == 0 or die $!; EOF } sub dist_core { my($self) = shift; my $make_frag = ''; foreach my $target (qw(dist tardist uutardist tarfile zipdist zipfile shdist)) { my $method = $target.'_target'; $make_frag .= "\n"; $make_frag .= $self->$method(); } return $make_frag; } sub dist_target { my($self) = shift; my $date_check = $self->oneliner(<<'CODE', ['-l']); print 'Warning: Makefile possibly out of date with $(VERSION_FROM)' if -e '$(VERSION_FROM)' and -M '$(VERSION_FROM)' < -M '$(FIRST_MAKEFILE)'; CODE return sprintf <<'MAKE_FRAG', $date_check; dist : $(DIST_DEFAULT) $(FIRST_MAKEFILE) $(NOECHO) %s MAKE_FRAG } sub tardist_target { my($self) = shift; return <<'MAKE_FRAG'; tardist : $(DISTVNAME).tar$(SUFFIX) $(NOECHO) $(NOOP) MAKE_FRAG } sub zipdist_target { my($self) = shift; return <<'MAKE_FRAG'; zipdist : $(DISTVNAME).zip $(NOECHO) $(NOOP) MAKE_FRAG } sub tarfile_target { my($self) = shift; return <<'MAKE_FRAG'; $(DISTVNAME).tar$(SUFFIX) : distdir $(PREOP) $(TO_UNIX) $(TAR) $(TARFLAGS) $(DISTVNAME).tar $(DISTVNAME) $(RM_RF) $(DISTVNAME) $(COMPRESS) $(DISTVNAME).tar $(NOECHO) $(ECHO) 'Created $(DISTVNAME).tar$(SUFFIX)' $(POSTOP) MAKE_FRAG } sub zipfile_target { my($self) = shift; return <<'MAKE_FRAG'; $(DISTVNAME).zip : distdir $(PREOP) $(ZIP) $(ZIPFLAGS) $(DISTVNAME).zip $(DISTVNAME) $(RM_RF) $(DISTVNAME) $(NOECHO) $(ECHO) 'Created $(DISTVNAME).zip' $(POSTOP) MAKE_FRAG } sub uutardist_target { my($self) = shift; return <<'MAKE_FRAG'; uutardist : $(DISTVNAME).tar$(SUFFIX) uuencode $(DISTVNAME).tar$(SUFFIX) $(DISTVNAME).tar$(SUFFIX) > $(DISTVNAME).tar$(SUFFIX)_uu $(NOECHO) $(ECHO) 'Created $(DISTVNAME).tar$(SUFFIX)_uu' MAKE_FRAG } sub shdist_target { my($self) = shift; return <<'MAKE_FRAG'; shdist : distdir $(PREOP) $(SHAR) $(DISTVNAME) > $(DISTVNAME).shar $(RM_RF) $(DISTVNAME) $(NOECHO) $(ECHO) 'Created $(DISTVNAME).shar' $(POSTOP) MAKE_FRAG } sub dlsyms { return ''; } sub dynamic_bs { my($self, %attribs) = @_; return "\nBOOTSTRAP =\n" unless $self->has_link_code(); my @exts; if ($self->{XSMULTI}) { @exts = $self->_xs_list_basenames; } else { @exts = '$(BASEEXT)'; } return join "\n", "BOOTSTRAP = @{[map { qq{$_.bs} } @exts]}\n", map { $self->_xs_make_bs($_) } @exts; } sub _xs_make_bs { my ($self, $basename) = @_; my ($v, $d, $f) = File::Spec->splitpath($basename); my @d = File::Spec->splitdir($d); shift @d if $self->{XSMULTI} and $d[0] eq 'lib'; my $instdir = $self->catdir('$(INST_ARCHLIB)', 'auto', @d, $f); $instdir = '$(INST_ARCHAUTODIR)' if $basename eq '$(BASEEXT)'; my $instfile = $self->catfile($instdir, "$f.bs"); my $exists = "$instdir\$(DFSEP).exists"; # match blibdirs_target # 1 2 3 return _sprintf562 <<'MAKE_FRAG', $basename, $instfile, $exists; # As Mkbootstrap might not write a file (if none is required) # we use touch to prevent make continually trying to remake it. # The DynaLoader only reads a non-empty file. %1$s.bs : $(FIRST_MAKEFILE) $(BOOTDEP) $(NOECHO) $(ECHO) "Running Mkbootstrap for %1$s ($(BSLOADLIBS))" $(NOECHO) $(PERLRUN) \ "-MExtUtils::Mkbootstrap" \ -e "Mkbootstrap('%1$s','$(BSLOADLIBS)');" $(NOECHO) $(TOUCH) "%1$s.bs" $(CHMOD) $(PERM_RW) "%1$s.bs" %2$s : %1$s.bs %3$s $(NOECHO) $(RM_RF) %2$s - $(CP_NONEMPTY) %1$s.bs %2$s $(PERM_RW) MAKE_FRAG } sub dynamic_lib { my($self, %attribs) = @_; return '' unless $self->needs_linking(); #might be because of a subdir return '' unless $self->has_link_code; my @m = $self->xs_dynamic_lib_macros(\%attribs); my @libs; my $dlsyms_ext = eval { $self->xs_dlsyms_ext }; if ($self->{XSMULTI}) { my @exts = $self->_xs_list_basenames; for my $ext (@exts) { my ($v, $d, $f) = File::Spec->splitpath($ext); my @d = File::Spec->splitdir($d); shift @d if $d[0] eq 'lib'; my $instdir = $self->catdir('$(INST_ARCHLIB)', 'auto', @d, $f); # Dynamic library names may need special handling. eval { require DynaLoader }; if (defined &DynaLoader::mod2fname) { $f = &DynaLoader::mod2fname([@d, $f]); } my $instfile = $self->catfile($instdir, "$f.\$(DLEXT)"); my $objfile = $self->_xsbuild_value('xs', $ext, 'OBJECT'); $objfile = "$ext\$(OBJ_EXT)" unless defined $objfile; my $ldfrom = $self->_xsbuild_value('xs', $ext, 'LDFROM'); $ldfrom = $objfile unless defined $ldfrom; my $exportlist = "$ext.def"; my @libchunk = ($objfile, $instfile, $instdir, $ldfrom, $exportlist); push @libchunk, $dlsyms_ext ? $ext.$dlsyms_ext : undef; push @libs, \@libchunk; } } else { my @libchunk = qw($(OBJECT) $(INST_DYNAMIC) $(INST_ARCHAUTODIR) $(LDFROM) $(EXPORT_LIST)); push @libchunk, $dlsyms_ext ? '$(BASEEXT)'.$dlsyms_ext : undef; @libs = (\@libchunk); } push @m, map { $self->xs_make_dynamic_lib(\%attribs, @$_); } @libs; return join("\n",@m); } sub xs_dynamic_lib_macros { my ($self, $attribs) = @_; my $otherldflags = $attribs->{OTHERLDFLAGS} || ""; my $inst_dynamic_dep = $attribs->{INST_DYNAMIC_DEP} || ""; my $armaybe = $self->_xs_armaybe($attribs); my $ld_opt = $Is{OS2} ? '$(OPTIMIZE) ' : ''; # Useful on other systems too? my $ld_fix = $Is{OS2} ? '|| ( $(RM_F) $@ && sh -c false )' : ''; sprintf <<'EOF', $armaybe, $ld_opt.$otherldflags, $inst_dynamic_dep, $ld_fix; # This section creates the dynamically loadable objects from relevant # objects and possibly $(MYEXTLIB). ARMAYBE = %s OTHERLDFLAGS = %s INST_DYNAMIC_DEP = %s INST_DYNAMIC_FIX = %s EOF } sub _xs_armaybe { my ($self, $attribs) = @_; my $armaybe = $attribs->{ARMAYBE} || $self->{ARMAYBE} || ":"; $armaybe = 'ar' if ($Is{OSF} and $armaybe eq ':'); $armaybe; } sub xs_make_dynamic_lib { my ($self, $attribs, $object, $to, $todir, $ldfrom, $exportlist, $dlsyms) = @_; $exportlist = '' if $exportlist ne '$(EXPORT_LIST)'; my $armaybe = $self->_xs_armaybe($attribs); my @m = sprintf '%s : %s $(MYEXTLIB) %s$(DFSEP).exists %s $(PERL_ARCHIVEDEP) $(PERL_ARCHIVE_AFTER) $(INST_DYNAMIC_DEP) %s'."\n", $to, $object, $todir, $exportlist, ($dlsyms || ''); my $dlsyms_arg = $self->xs_dlsyms_arg($dlsyms); if ($armaybe ne ':'){ $ldfrom = 'tmp$(LIB_EXT)'; push(@m," \$(ARMAYBE) cr $ldfrom $object\n"); push(@m," \$(RANLIB) $ldfrom\n"); } $ldfrom = "-all $ldfrom -none" if $Is{OSF}; # The IRIX linker doesn't use LD_RUN_PATH my $ldrun = $Is{IRIX} && $self->{LD_RUN_PATH} ? qq{-rpath "$self->{LD_RUN_PATH}"} : ''; # For example in AIX the shared objects/libraries from previous builds # linger quite a while in the shared dynalinker cache even when nobody # is using them. This is painful if one for instance tries to restart # a failed build because the link command will fail unnecessarily 'cos # the shared object/library is 'busy'. push(@m," \$(RM_F) \$\@\n"); my $libs = '$(LDLOADLIBS)'; if (($Is{NetBSD} || $Is{Interix} || $Is{Android}) && $Config{'useshrplib'} eq 'true') { # Use nothing on static perl platforms, and to the flags needed # to link against the shared libperl library on shared perl # platforms. We peek at lddlflags to see if we need -Wl,-R # or -R to add paths to the run-time library search path. if ($Config{'lddlflags'} =~ /-Wl,-R/) { $libs .= ' "-L$(PERL_INC)" "-Wl,-R$(INSTALLARCHLIB)/CORE" "-Wl,-R$(PERL_ARCHLIB)/CORE" -lperl'; } elsif ($Config{'lddlflags'} =~ /-R/) { $libs .= ' "-L$(PERL_INC)" "-R$(INSTALLARCHLIB)/CORE" "-R$(PERL_ARCHLIB)/CORE" -lperl'; } elsif ( $Is{Android} ) { # The Android linker will not recognize symbols from # libperl unless the module explicitly depends on it. $libs .= ' "-L$(PERL_INC)" -lperl'; } } my $ld_run_path_shell = ""; if ($self->{LD_RUN_PATH} ne "") { $ld_run_path_shell = 'LD_RUN_PATH="$(LD_RUN_PATH)" '; } push @m, sprintf <<'MAKE', $ld_run_path_shell, $ldrun, $dlsyms_arg, $ldfrom, $self->xs_obj_opt('$@'), $libs, $exportlist; %s$(LD) %s $(LDDLFLAGS) %s %s $(OTHERLDFLAGS) %s $(MYEXTLIB) \ $(PERL_ARCHIVE) %s $(PERL_ARCHIVE_AFTER) %s \ $(INST_DYNAMIC_FIX) $(CHMOD) $(PERM_RWX) $@ MAKE join '', @m; } sub exescan { my($self,$path) = @_; $path; } sub extliblist { my($self,$libs) = @_; require ExtUtils::Liblist; $self->ext($libs, $Verbose); } sub find_perl { my($self, $ver, $names, $dirs, $trace) = @_; if ($trace >= 2){ print "Looking for perl $ver by these names: @$names in these dirs: @$dirs "; } my $stderr_duped = 0; local *STDERR_COPY; unless ($Is{BSD}) { # >& and lexical filehandles together give 5.6.2 indigestion if( open(STDERR_COPY, '>&STDERR') ) { ## no critic $stderr_duped = 1; } else { warn <file_name_is_absolute($name)) { # /foo/bar $abs = $name; } elsif ($self->canonpath($name) eq $self->canonpath(basename($name))) { # foo $use_dir = 1; } else { # foo/bar $abs = $self->catfile($Curdir, $name); } foreach my $dir ($use_dir ? @$dirs : 1){ next unless defined $dir; # $self->{PERL_SRC} may be undefined $abs = $self->catfile($dir, $name) if $use_dir; print "Checking $abs\n" if ($trace >= 2); next unless $self->maybe_command($abs); print "Executing $abs\n" if ($trace >= 2); my $val; my $version_check = qq{"$abs" -le "require $ver; print qq{VER_OK}"}; # To avoid using the unportable 2>&1 to suppress STDERR, # we close it before running the command. # However, thanks to a thread library bug in many BSDs # ( http://www.freebsd.org/cgi/query-pr.cgi?pr=51535 ) # we cannot use the fancier more portable way in here # but instead need to use the traditional 2>&1 construct. if ($Is{BSD}) { $val = `$version_check 2>&1`; } else { close STDERR if $stderr_duped; $val = `$version_check`; # 5.6.2's 3-arg open doesn't work with >& open STDERR, ">&STDERR_COPY" ## no critic if $stderr_duped; } if ($val =~ /^VER_OK/m) { print "Using PERL=$abs\n" if $trace; return $abs; } elsif ($trace >= 2) { print "Result: '$val' ".($? >> 8)."\n"; } } } print "Unable to find a perl $ver (by these names: @$names, in these dirs: @$dirs)\n"; 0; # false and not empty } sub fixin { # stolen from the pink Camel book, more or less my ( $self, @files ) = @_; for my $file (@files) { my $file_new = "$file.new"; my $file_bak = "$file.bak"; open( my $fixin, '<', $file ) or croak "Can't process '$file': $!"; local $/ = "\n"; chomp( my $line = <$fixin> ); next unless $line =~ s/^\s*\#!\s*//; # Not a shebang file. my $shb = $self->_fixin_replace_shebang( $file, $line ); next unless defined $shb; open( my $fixout, ">", "$file_new" ) or do { warn "Can't create new $file: $!\n"; next; }; # Print out the new #! line (or equivalent). local $\; local $/; print $fixout $shb, <$fixin>; close $fixin; close $fixout; chmod 0666, $file_bak; unlink $file_bak; unless ( _rename( $file, $file_bak ) ) { warn "Can't rename $file to $file_bak: $!"; next; } unless ( _rename( $file_new, $file ) ) { warn "Can't rename $file_new to $file: $!"; unless ( _rename( $file_bak, $file ) ) { warn "Can't rename $file_bak back to $file either: $!"; warn "Leaving $file renamed as $file_bak\n"; } next; } unlink $file_bak; } continue { system("$Config{'eunicefix'} $file") if $Config{'eunicefix'} ne ':'; } } sub _rename { my($old, $new) = @_; foreach my $file ($old, $new) { if( $Is{VMS} and basename($file) !~ /\./ ) { # rename() in 5.8.0 on VMS will not rename a file if it # does not contain a dot yet it returns success. $file = "$file."; } } return rename($old, $new); } sub _fixin_replace_shebang { my ( $self, $file, $line ) = @_; # Now figure out the interpreter name. my ( $cmd, $arg ) = split ' ', $line, 2; $cmd =~ s!^.*/!!; # Now look (in reverse) for interpreter in absolute PATH (unless perl). my $interpreter; if ( $cmd =~ m{^perl(?:\z|[^a-z])} ) { if ( $Config{startperl} =~ m,^\#!.*/perl, ) { $interpreter = $Config{startperl}; $interpreter =~ s,^\#!,,; } else { $interpreter = $Config{perlpath}; } } else { my (@absdirs) = reverse grep { $self->file_name_is_absolute($_) } $self->path; $interpreter = ''; foreach my $dir (@absdirs) { my $maybefile = $self->catfile($dir,$cmd); if ( $self->maybe_command($maybefile) ) { warn "Ignoring $interpreter in $file\n" if $Verbose && $interpreter; $interpreter = $maybefile; } } } # Figure out how to invoke interpreter on this machine. my ($does_shbang) = $Config{'sharpbang'} =~ /^\s*\#\!/; my ($shb) = ""; if ($interpreter) { print "Changing sharpbang in $file to $interpreter" if $Verbose; # this is probably value-free on DOSISH platforms if ($does_shbang) { $shb .= "$Config{'sharpbang'}$interpreter"; $shb .= ' ' . $arg if defined $arg; $shb .= "\n"; } } else { warn "Can't find $cmd in PATH, $file unchanged" if $Verbose; return; } return $shb } sub force { my($self) = shift; '# Phony target to force checking subdirectories. FORCE : $(NOECHO) $(NOOP) '; } # '; sub guess_name { my($self) = @_; use Cwd 'cwd'; my $name = basename(cwd()); $name =~ s|[\-_][\d\.\-]+\z||; # this is new with MM 5.00, we # strip minus or underline # followed by a float or some such print "Warning: Guessing NAME [$name] from current directory name.\n"; $name; } sub has_link_code { my($self) = shift; return $self->{HAS_LINK_CODE} if defined $self->{HAS_LINK_CODE}; if ($self->{OBJECT} or @{$self->{C} || []} or $self->{MYEXTLIB}){ $self->{HAS_LINK_CODE} = 1; return 1; } return $self->{HAS_LINK_CODE} = 0; } sub init_dirscan { # --- File and Directory Lists (.xs .pm .pod etc) my($self) = @_; my(%dir, %xs, %c, %o, %h, %pl_files, %pm); my %ignore = map {( $_ => 1 )} qw(Makefile.PL Build.PL test.pl t); # ignore the distdir $Is{VMS} ? $ignore{"$self->{DISTVNAME}.dir"} = 1 : $ignore{$self->{DISTVNAME}} = 1; my $distprefix = $Is{VMS} ? qr/^\Q$self->{DISTNAME}\E-v?[\d\.]+\.dir$/i : qr/^\Q$self->{DISTNAME}\E-v?[\d\.]+$/; @ignore{map lc, keys %ignore} = values %ignore if $Is{VMS}; if ( defined $self->{XS} and !defined $self->{C} ) { my @c_files = grep { m/\.c(pp|xx)?\z/i } values %{$self->{XS}}; my @o_files = grep { m/(?:.(?:o(?:bj)?)|\$\(OBJ_EXT\))\z/i } values %{$self->{XS}}; %c = map { $_ => 1 } @c_files; %o = map { $_ => 1 } @o_files; } foreach my $name ($self->lsdir($Curdir)){ next if $name =~ /\#/; next if $name =~ $distprefix && -d $name; $name = lc($name) if $Is{VMS}; next if $name eq $Curdir or $name eq $Updir or $ignore{$name}; next unless $self->libscan($name); if (-d $name){ next if -l $name; # We do not support symlinks at all next if $self->{NORECURS}; $dir{$name} = $name if (-f $self->catfile($name,"Makefile.PL")); } elsif ($name =~ /\.xs\z/){ my($c); ($c = $name) =~ s/\.xs\z/.c/; $xs{$name} = $c; $c{$c} = 1; } elsif ($name =~ /\.c(pp|xx|c)?\z/i){ # .c .C .cpp .cxx .cc $c{$name} = 1 unless $name =~ m/perlmain\.c/; # See MAP_TARGET } elsif ($name =~ /\.h\z/i){ $h{$name} = 1; } elsif ($name =~ /\.PL\z/) { ($pl_files{$name} = $name) =~ s/\.PL\z// ; } elsif (($Is{VMS} || $Is{Dos}) && $name =~ /[._]pl$/i) { # case-insensitive filesystem, one dot per name, so foo.h.PL # under Unix appears as foo.h_pl under VMS or fooh.pl on Dos local($/); open(my $pl, '<', $name); my $txt = <$pl>; close $pl; if ($txt =~ /Extracting \S+ \(with variable substitutions/) { ($pl_files{$name} = $name) =~ s/[._]pl\z//i ; } else { $pm{$name} = $self->catfile($self->{INST_LIBDIR},$name); } } elsif ($name =~ /\.(p[ml]|pod)\z/){ $pm{$name} = $self->catfile($self->{INST_LIBDIR},$name); } } $self->{PL_FILES} ||= \%pl_files; $self->{DIR} ||= [sort keys %dir]; $self->{XS} ||= \%xs; $self->{C} ||= [sort keys %c]; $self->{H} ||= [sort keys %h]; $self->{PM} ||= \%pm; my @o_files = @{$self->{C}}; %o = (%o, map { $_ => 1 } grep s/\.c(pp|xx|c)?\z/$self->{OBJ_EXT}/i, @o_files); $self->{O_FILES} = [sort keys %o]; } sub init_MANPODS { my $self = shift; # Set up names of manual pages to generate from pods foreach my $man (qw(MAN1 MAN3)) { if ( $self->{"${man}PODS"} or $self->{"INSTALL${man}DIR"} =~ /^(none|\s*)$/ ) { $self->{"${man}PODS"} ||= {}; } else { my $init_method = "init_${man}PODS"; $self->$init_method(); } } } sub _has_pod { my($self, $file) = @_; my($ispod)=0; if (open( my $fh, '<', $file )) { while (<$fh>) { if (/^=(?:head\d+|item|pod)\b/) { $ispod=1; last; } } close $fh; } else { # If it doesn't exist yet, we assume, it has pods in it $ispod = 1; } return $ispod; } sub init_MAN1PODS { my($self) = @_; if ( exists $self->{EXE_FILES} ) { foreach my $name (@{$self->{EXE_FILES}}) { next unless $self->_has_pod($name); $self->{MAN1PODS}->{$name} = $self->catfile("\$(INST_MAN1DIR)", basename($name).".\$(MAN1EXT)"); } } } sub init_MAN3PODS { my $self = shift; my %manifypods = (); # we collect the keys first, i.e. the files # we have to convert to pod foreach my $name (keys %{$self->{PM}}) { if ($name =~ /\.pod\z/ ) { $manifypods{$name} = $self->{PM}{$name}; } elsif ($name =~ /\.p[ml]\z/ ) { if( $self->_has_pod($name) ) { $manifypods{$name} = $self->{PM}{$name}; } } } my $parentlibs_re = join '|', @{$self->{PMLIBPARENTDIRS}}; # Remove "Configure.pm" and similar, if it's not the only pod listed # To force inclusion, just name it "Configure.pod", or override # MAN3PODS foreach my $name (keys %manifypods) { if ( ($self->{PERL_CORE} and $name =~ /(config|setup).*\.pm/is) or ( $name =~ m/^README\.pod$/i ) # don't manify top-level README.pod ) { delete $manifypods{$name}; next; } my($manpagename) = $name; $manpagename =~ s/\.p(od|m|l)\z//; # everything below lib is ok unless($manpagename =~ s!^\W*($parentlibs_re)\W+!!s) { $manpagename = $self->catfile( split(/::/,$self->{PARENT_NAME}),$manpagename ); } $manpagename = $self->replace_manpage_separator($manpagename); $self->{MAN3PODS}->{$name} = $self->catfile("\$(INST_MAN3DIR)", "$manpagename.\$(MAN3EXT)"); } } sub init_PM { my $self = shift; # Some larger extensions often wish to install a number of *.pm/pl # files into the library in various locations. # The attribute PMLIBDIRS holds an array reference which lists # subdirectories which we should search for library files to # install. PMLIBDIRS defaults to [ 'lib', $self->{BASEEXT} ]. We # recursively search through the named directories (skipping any # which don't exist or contain Makefile.PL files). # For each *.pm or *.pl file found $self->libscan() is called with # the default installation path in $_[1]. The return value of # libscan defines the actual installation location. The default # libscan function simply returns the path. The file is skipped # if libscan returns false. # The default installation location passed to libscan in $_[1] is: # # ./*.pm => $(INST_LIBDIR)/*.pm # ./xyz/... => $(INST_LIBDIR)/xyz/... # ./lib/... => $(INST_LIB)/... # # In this way the 'lib' directory is seen as the root of the actual # perl library whereas the others are relative to INST_LIBDIR # (which includes PARENT_NAME). This is a subtle distinction but one # that's important for nested modules. unless( $self->{PMLIBDIRS} ) { if( $Is{VMS} ) { # Avoid logical name vs directory collisions $self->{PMLIBDIRS} = ['./lib', "./$self->{BASEEXT}"]; } else { $self->{PMLIBDIRS} = ['lib', $self->{BASEEXT}]; } } #only existing directories that aren't in $dir are allowed # Avoid $_ wherever possible: # @{$self->{PMLIBDIRS}} = grep -d && !$dir{$_}, @{$self->{PMLIBDIRS}}; my (@pmlibdirs) = @{$self->{PMLIBDIRS}}; @{$self->{PMLIBDIRS}} = (); my %dir = map { ($_ => $_) } @{$self->{DIR}}; foreach my $pmlibdir (@pmlibdirs) { -d $pmlibdir && !$dir{$pmlibdir} && push @{$self->{PMLIBDIRS}}, $pmlibdir; } unless( $self->{PMLIBPARENTDIRS} ) { @{$self->{PMLIBPARENTDIRS}} = ('lib'); } return if $self->{PM} and $self->{ARGS}{PM}; if (@{$self->{PMLIBDIRS}}){ print "Searching PMLIBDIRS: @{$self->{PMLIBDIRS}}\n" if ($Verbose >= 2); require File::Find; File::Find::find(sub { if (-d $_){ unless ($self->libscan($_)){ $File::Find::prune = 1; } return; } return if /\#/; return if /~$/; # emacs temp files return if /,v$/; # RCS files return if m{\.swp$}; # vim swap files my $path = $File::Find::name; my $prefix = $self->{INST_LIBDIR}; my $striplibpath; my $parentlibs_re = join '|', @{$self->{PMLIBPARENTDIRS}}; $prefix = $self->{INST_LIB} if ($striplibpath = $path) =~ s{^(\W*)($parentlibs_re)\W} {$1}i; my($inst) = $self->catfile($prefix,$striplibpath); local($_) = $inst; # for backwards compatibility $inst = $self->libscan($inst); print "libscan($path) => '$inst'\n" if ($Verbose >= 2); return unless $inst; if ($self->{XSMULTI} and $inst =~ /\.xs\z/) { my($base); ($base = $path) =~ s/\.xs\z//; $self->{XS}{$path} = "$base.c"; push @{$self->{C}}, "$base.c"; push @{$self->{O_FILES}}, "$base$self->{OBJ_EXT}"; } else { $self->{PM}{$path} = $inst; } }, @{$self->{PMLIBDIRS}}); } } sub init_DIRFILESEP { my($self) = shift; $self->{DIRFILESEP} = '/'; } sub init_main { my($self) = @_; # --- Initialize Module Name and Paths # NAME = Foo::Bar::Oracle # FULLEXT = Foo/Bar/Oracle # BASEEXT = Oracle # PARENT_NAME = Foo::Bar ### Only UNIX: ### ($self->{FULLEXT} = ### $self->{NAME}) =~ s!::!/!g ; #eg. BSD/Foo/Socket $self->{FULLEXT} = $self->catdir(split /::/, $self->{NAME}); # Copied from DynaLoader: my(@modparts) = split(/::/,$self->{NAME}); my($modfname) = $modparts[-1]; # Some systems have restrictions on files names for DLL's etc. # mod2fname returns appropriate file base name (typically truncated) # It may also edit @modparts if required. # We require DynaLoader to make sure that mod2fname is loaded eval { require DynaLoader }; if (defined &DynaLoader::mod2fname) { $modfname = &DynaLoader::mod2fname(\@modparts); } ($self->{PARENT_NAME}, $self->{BASEEXT}) = $self->{NAME} =~ m!(?:([\w:]+)::)?(\w+)\z! ; $self->{PARENT_NAME} ||= ''; if (defined &DynaLoader::mod2fname) { # As of 5.001m, dl_os2 appends '_' $self->{DLBASE} = $modfname; } else { $self->{DLBASE} = '$(BASEEXT)'; } # --- Initialize PERL_LIB, PERL_SRC # *Real* information: where did we get these two from? ... my $inc_config_dir = dirname($INC{'Config.pm'}); my $inc_carp_dir = dirname($INC{'Carp.pm'}); unless ($self->{PERL_SRC}){ foreach my $dir_count (1..8) { # 8 is the VMS limit for nesting my $dir = $self->catdir(($Updir) x $dir_count); if (-f $self->catfile($dir,"config_h.SH") && -f $self->catfile($dir,"perl.h") && -f $self->catfile($dir,"lib","strict.pm") ) { $self->{PERL_SRC}=$dir ; last; } } } warn "PERL_CORE is set but I can't find your PERL_SRC!\n" if $self->{PERL_CORE} and !$self->{PERL_SRC}; if ($self->{PERL_SRC}){ $self->{PERL_LIB} ||= $self->catdir("$self->{PERL_SRC}","lib"); $self->{PERL_ARCHLIB} = $self->{PERL_LIB}; $self->{PERL_INC} = ($Is{Win32}) ? $self->catdir($self->{PERL_LIB},"CORE") : $self->{PERL_SRC}; # catch a situation that has occurred a few times in the past: unless ( -s $self->catfile($self->{PERL_SRC},'cflags') or $Is{VMS} && -s $self->catfile($self->{PERL_SRC},'vmsish.h') or $Is{Win32} ){ warn qq{ You cannot build extensions below the perl source tree after executing a 'make clean' in the perl source tree. To rebuild extensions distributed with the perl source you should simply Configure (to include those extensions) and then build perl as normal. After installing perl the source tree can be deleted. It is not needed for building extensions by running 'perl Makefile.PL' usually without extra arguments. It is recommended that you unpack and build additional extensions away from the perl source tree. }; } } else { # we should also consider $ENV{PERL5LIB} here my $old = $self->{PERL_LIB} || $self->{PERL_ARCHLIB} || $self->{PERL_INC}; $self->{PERL_LIB} ||= $Config{privlibexp}; $self->{PERL_ARCHLIB} ||= $Config{archlibexp}; $self->{PERL_INC} = $self->catdir("$self->{PERL_ARCHLIB}","CORE"); # wild guess for now my $perl_h; if (not -f ($perl_h = $self->catfile($self->{PERL_INC},"perl.h")) and not $old){ # Maybe somebody tries to build an extension with an # uninstalled Perl outside of Perl build tree my $lib; for my $dir (@INC) { $lib = $dir, last if -e $self->catfile($dir, "Config.pm"); } if ($lib) { # Win32 puts its header files in /perl/src/lib/CORE. # Unix leaves them in /perl/src. my $inc = $Is{Win32} ? $self->catdir($lib, "CORE" ) : dirname $lib; if (-e $self->catfile($inc, "perl.h")) { $self->{PERL_LIB} = $lib; $self->{PERL_ARCHLIB} = $lib; $self->{PERL_INC} = $inc; $self->{UNINSTALLED_PERL} = 1; print <{PERL_LIB} = File::Spec->rel2abs($self->{PERL_LIB}); $self->{PERL_ARCHLIB} = File::Spec->rel2abs($self->{PERL_ARCHLIB}); } $self->{PERL_INCDEP} = $self->{PERL_INC}; $self->{PERL_ARCHLIBDEP} = $self->{PERL_ARCHLIB}; # We get SITELIBEXP and SITEARCHEXP directly via # Get_from_Config. When we are running standard modules, these # won't matter, we will set INSTALLDIRS to "perl". Otherwise we # set it to "site". I prefer that INSTALLDIRS be set from outside # MakeMaker. $self->{INSTALLDIRS} ||= "site"; $self->{MAN1EXT} ||= $Config{man1ext}; $self->{MAN3EXT} ||= $Config{man3ext}; # Get some stuff out of %Config if we haven't yet done so print "CONFIG must be an array ref\n" if ($self->{CONFIG} and ref $self->{CONFIG} ne 'ARRAY'); $self->{CONFIG} = [] unless (ref $self->{CONFIG}); push(@{$self->{CONFIG}}, @ExtUtils::MakeMaker::Get_from_Config); push(@{$self->{CONFIG}}, 'shellflags') if $Config{shellflags}; my(%once_only); foreach my $m (@{$self->{CONFIG}}){ next if $once_only{$m}; print "CONFIG key '$m' does not exist in Config.pm\n" unless exists $Config{$m}; $self->{uc $m} ||= $Config{$m}; $once_only{$m} = 1; } # This is too dangerous: # if ($^O eq "next") { # $self->{AR} = "libtool"; # $self->{AR_STATIC_ARGS} = "-o"; # } # But I leave it as a placeholder $self->{AR_STATIC_ARGS} ||= "cr"; # These should never be needed $self->{OBJ_EXT} ||= '.o'; $self->{LIB_EXT} ||= '.a'; $self->{MAP_TARGET} ||= "perl"; $self->{LIBPERL_A} ||= "libperl$self->{LIB_EXT}"; # make a simple check if we find strict warn "Warning: PERL_LIB ($self->{PERL_LIB}) seems not to be a perl library directory (strict.pm not found)" unless -f $self->catfile("$self->{PERL_LIB}","strict.pm") || $self->{NAME} eq "ExtUtils::MakeMaker"; } sub init_tools { my $self = shift; $self->{ECHO} ||= 'echo'; $self->{ECHO_N} ||= 'echo -n'; $self->{RM_F} ||= "rm -f"; $self->{RM_RF} ||= "rm -rf"; $self->{TOUCH} ||= "touch"; $self->{TEST_F} ||= "test -f"; $self->{TEST_S} ||= "test -s"; $self->{CP} ||= "cp"; $self->{MV} ||= "mv"; $self->{CHMOD} ||= "chmod"; $self->{FALSE} ||= 'false'; $self->{TRUE} ||= 'true'; $self->{LD} ||= 'ld'; return $self->SUPER::init_tools(@_); # After SUPER::init_tools so $Config{shell} has a # chance to get set. $self->{SHELL} ||= '/bin/sh'; return; } sub init_linker { my($self) = shift; $self->{PERL_ARCHIVE} ||= ''; $self->{PERL_ARCHIVEDEP} ||= ''; $self->{PERL_ARCHIVE_AFTER} ||= ''; $self->{EXPORT_LIST} ||= ''; } sub init_lib2arch { my($self) = shift; # The user who requests an installation directory explicitly # should not have to tell us an architecture installation directory # as well. We look if a directory exists that is named after the # architecture. If not we take it as a sign that it should be the # same as the requested installation directory. Otherwise we take # the found one. for my $libpair ({l=>"privlib", a=>"archlib"}, {l=>"sitelib", a=>"sitearch"}, {l=>"vendorlib", a=>"vendorarch"}, ) { my $lib = "install$libpair->{l}"; my $Lib = uc $lib; my $Arch = uc "install$libpair->{a}"; if( $self->{$Lib} && ! $self->{$Arch} ){ my($ilib) = $Config{$lib}; $self->prefixify($Arch,$ilib,$self->{$Lib}); unless (-d $self->{$Arch}) { print "Directory $self->{$Arch} not found\n" if $Verbose; $self->{$Arch} = $self->{$Lib}; } print "Defaulting $Arch to $self->{$Arch}\n" if $Verbose; } } } sub init_PERL { my($self) = shift; my @defpath = (); foreach my $component ($self->{PERL_SRC}, $self->path(), $Config{binexp}) { push @defpath, $component if defined $component; } # Build up a set of file names (not command names). my $thisperl = $self->canonpath($^X); $thisperl .= $Config{exe_ext} unless # VMS might have a file version # at the end $Is{VMS} ? $thisperl =~ m/$Config{exe_ext}(;\d+)?$/i : $thisperl =~ m/$Config{exe_ext}$/i; # We need a relative path to perl when in the core. $thisperl = $self->abs2rel($thisperl) if $self->{PERL_CORE}; my @perls = ($thisperl); push @perls, map { "$_$Config{exe_ext}" } ("perl$Config{version}", 'perl5', 'perl'); # miniperl has priority over all but the canonical perl when in the # core. Otherwise its a last resort. my $miniperl = "miniperl$Config{exe_ext}"; if( $self->{PERL_CORE} ) { splice @perls, 1, 0, $miniperl; } else { push @perls, $miniperl; } $self->{PERL} ||= $self->find_perl(5.0, \@perls, \@defpath, $Verbose ); my $perl = $self->{PERL}; $perl =~ s/^"//; my $has_mcr = $perl =~ s/^MCR\s*//; my $perlflags = ''; my $stripped_perl; while ($perl) { ($stripped_perl = $perl) =~ s/"$//; last if -x $stripped_perl; last unless $perl =~ s/(\s+\S+)$//; $perlflags = $1.$perlflags; } $self->{PERL} = $stripped_perl; $self->{PERL} = 'MCR '.$self->{PERL} if $has_mcr || $Is{VMS}; # When built for debugging, VMS doesn't create perl.exe but ndbgperl.exe. my $perl_name = 'perl'; $perl_name = 'ndbgperl' if $Is{VMS} && defined $Config{usevmsdebug} && $Config{usevmsdebug} eq 'define'; # XXX This logic is flawed. If "miniperl" is anywhere in the path # it will get confused. It should be fixed to work only on the filename. # Define 'FULLPERL' to be a non-miniperl (used in test: target) unless ($self->{FULLPERL}) { ($self->{FULLPERL} = $self->{PERL}) =~ s/\Q$miniperl\E$/$perl_name$Config{exe_ext}/i; $self->{FULLPERL} = qq{"$self->{FULLPERL}"}.$perlflags; } # Can't have an image name with quotes, and findperl will have # already escaped spaces. $self->{FULLPERL} =~ tr/"//d if $Is{VMS}; # Little hack to get around VMS's find_perl putting "MCR" in front # sometimes. $self->{ABSPERL} = $self->{PERL}; $has_mcr = $self->{ABSPERL} =~ s/^MCR\s*//; if( $self->file_name_is_absolute($self->{ABSPERL}) ) { $self->{ABSPERL} = '$(PERL)'; } else { $self->{ABSPERL} = $self->rel2abs($self->{ABSPERL}); # Quote the perl command if it contains whitespace $self->{ABSPERL} = $self->quote_literal($self->{ABSPERL}) if $self->{ABSPERL} =~ /\s/; $self->{ABSPERL} = 'MCR '.$self->{ABSPERL} if $has_mcr; } $self->{PERL} = qq{"$self->{PERL}"}.$perlflags; # Can't have an image name with quotes, and findperl will have # already escaped spaces. $self->{PERL} =~ tr/"//d if $Is{VMS}; # Are we building the core? $self->{PERL_CORE} = $ENV{PERL_CORE} unless exists $self->{PERL_CORE}; $self->{PERL_CORE} = 0 unless defined $self->{PERL_CORE}; # Make sure perl can find itself before it's installed. my $lib_paths = $self->{UNINSTALLED_PERL} || $self->{PERL_CORE} ? ( $self->{PERL_ARCHLIB} && $self->{PERL_LIB} && $self->{PERL_ARCHLIB} ne $self->{PERL_LIB} ) ? q{ "-I$(PERL_LIB)" "-I$(PERL_ARCHLIB)"} : q{ "-I$(PERL_LIB)"} : undef; my $inst_lib_paths = $self->{INST_ARCHLIB} ne $self->{INST_LIB} ? 'RUN)'.$perlflags.' "-I$(INST_ARCHLIB)" "-I$(INST_LIB)"' : 'RUN)'.$perlflags.' "-I$(INST_LIB)"'; # How do we run perl? foreach my $perl (qw(PERL FULLPERL ABSPERL)) { my $run = $perl.'RUN'; $self->{$run} = qq{\$($perl)}; $self->{$run} .= $lib_paths if $lib_paths; $self->{$perl.'RUNINST'} = '$('.$perl.$inst_lib_paths; } return 1; } sub init_platform { my($self) = shift; $self->{MM_Unix_VERSION} = $VERSION; $self->{PERL_MALLOC_DEF} = '-DPERL_EXTMALLOC_DEF -Dmalloc=Perl_malloc '. '-Dfree=Perl_mfree -Drealloc=Perl_realloc '. '-Dcalloc=Perl_calloc'; } sub platform_constants { my($self) = shift; my $make_frag = ''; foreach my $macro (qw(MM_Unix_VERSION PERL_MALLOC_DEF)) { next unless defined $self->{$macro}; $make_frag .= "$macro = $self->{$macro}\n"; } return $make_frag; } sub init_PERM { my($self) = shift; $self->{PERM_DIR} = 755 unless defined $self->{PERM_DIR}; $self->{PERM_RW} = 644 unless defined $self->{PERM_RW}; $self->{PERM_RWX} = 755 unless defined $self->{PERM_RWX}; return 1; } sub init_xs { my $self = shift; if ($self->has_link_code()) { $self->{INST_STATIC} = $self->catfile('$(INST_ARCHAUTODIR)', '$(BASEEXT)$(LIB_EXT)'); $self->{INST_DYNAMIC} = $self->catfile('$(INST_ARCHAUTODIR)', '$(DLBASE).$(DLEXT)'); $self->{INST_BOOT} = $self->catfile('$(INST_ARCHAUTODIR)', '$(BASEEXT).bs'); if ($self->{XSMULTI}) { my @exts = $self->_xs_list_basenames; my (@statics, @dynamics, @boots); for my $ext (@exts) { my ($v, $d, $f) = File::Spec->splitpath($ext); my @d = File::Spec->splitdir($d); shift @d if defined $d[0] and $d[0] eq 'lib'; my $instdir = $self->catdir('$(INST_ARCHLIB)', 'auto', @d, $f); my $instfile = $self->catfile($instdir, $f); push @statics, "$instfile\$(LIB_EXT)"; # Dynamic library names may need special handling. my $dynfile = $instfile; eval { require DynaLoader }; if (defined &DynaLoader::mod2fname) { $dynfile = $self->catfile($instdir, &DynaLoader::mod2fname([@d, $f])); } push @dynamics, "$dynfile.\$(DLEXT)"; push @boots, "$instfile.bs"; } $self->{INST_STATIC} = join ' ', @statics; $self->{INST_DYNAMIC} = join ' ', @dynamics; $self->{INST_BOOT} = join ' ', @boots; } } else { $self->{INST_STATIC} = ''; $self->{INST_DYNAMIC} = ''; $self->{INST_BOOT} = ''; } } sub install { my($self, %attribs) = @_; my(@m); push @m, q{ install :: pure_install doc_install $(NOECHO) $(NOOP) install_perl :: pure_perl_install doc_perl_install $(NOECHO) $(NOOP) install_site :: pure_site_install doc_site_install $(NOECHO) $(NOOP) install_vendor :: pure_vendor_install doc_vendor_install $(NOECHO) $(NOOP) pure_install :: pure_$(INSTALLDIRS)_install $(NOECHO) $(NOOP) doc_install :: doc_$(INSTALLDIRS)_install $(NOECHO) $(NOOP) pure__install : pure_site_install $(NOECHO) $(ECHO) INSTALLDIRS not defined, defaulting to INSTALLDIRS=site doc__install : doc_site_install $(NOECHO) $(ECHO) INSTALLDIRS not defined, defaulting to INSTALLDIRS=site pure_perl_install :: all $(NOECHO) $(MOD_INSTALL) \ }; push @m, q{ read "}.$self->catfile('$(PERL_ARCHLIB)','auto','$(FULLEXT)','.packlist').q{" \ write "}.$self->catfile('$(DESTINSTALLARCHLIB)','auto','$(FULLEXT)','.packlist').q{" \ } unless $self->{NO_PACKLIST}; push @m, q{ "$(INST_LIB)" "$(DESTINSTALLPRIVLIB)" \ "$(INST_ARCHLIB)" "$(DESTINSTALLARCHLIB)" \ "$(INST_BIN)" "$(DESTINSTALLBIN)" \ "$(INST_SCRIPT)" "$(DESTINSTALLSCRIPT)" \ "$(INST_MAN1DIR)" "$(DESTINSTALLMAN1DIR)" \ "$(INST_MAN3DIR)" "$(DESTINSTALLMAN3DIR)" $(NOECHO) $(WARN_IF_OLD_PACKLIST) \ "}.$self->catdir('$(SITEARCHEXP)','auto','$(FULLEXT)').q{" pure_site_install :: all $(NOECHO) $(MOD_INSTALL) \ }; push @m, q{ read "}.$self->catfile('$(SITEARCHEXP)','auto','$(FULLEXT)','.packlist').q{" \ write "}.$self->catfile('$(DESTINSTALLSITEARCH)','auto','$(FULLEXT)','.packlist').q{" \ } unless $self->{NO_PACKLIST}; push @m, q{ "$(INST_LIB)" "$(DESTINSTALLSITELIB)" \ "$(INST_ARCHLIB)" "$(DESTINSTALLSITEARCH)" \ "$(INST_BIN)" "$(DESTINSTALLSITEBIN)" \ "$(INST_SCRIPT)" "$(DESTINSTALLSITESCRIPT)" \ "$(INST_MAN1DIR)" "$(DESTINSTALLSITEMAN1DIR)" \ "$(INST_MAN3DIR)" "$(DESTINSTALLSITEMAN3DIR)" $(NOECHO) $(WARN_IF_OLD_PACKLIST) \ "}.$self->catdir('$(PERL_ARCHLIB)','auto','$(FULLEXT)').q{" pure_vendor_install :: all $(NOECHO) $(MOD_INSTALL) \ }; push @m, q{ read "}.$self->catfile('$(VENDORARCHEXP)','auto','$(FULLEXT)','.packlist').q{" \ write "}.$self->catfile('$(DESTINSTALLVENDORARCH)','auto','$(FULLEXT)','.packlist').q{" \ } unless $self->{NO_PACKLIST}; push @m, q{ "$(INST_LIB)" "$(DESTINSTALLVENDORLIB)" \ "$(INST_ARCHLIB)" "$(DESTINSTALLVENDORARCH)" \ "$(INST_BIN)" "$(DESTINSTALLVENDORBIN)" \ "$(INST_SCRIPT)" "$(DESTINSTALLVENDORSCRIPT)" \ "$(INST_MAN1DIR)" "$(DESTINSTALLVENDORMAN1DIR)" \ "$(INST_MAN3DIR)" "$(DESTINSTALLVENDORMAN3DIR)" }; push @m, q{ doc_perl_install :: all $(NOECHO) $(NOOP) doc_site_install :: all $(NOECHO) $(NOOP) doc_vendor_install :: all $(NOECHO) $(NOOP) } if $self->{NO_PERLLOCAL}; push @m, q{ doc_perl_install :: all $(NOECHO) $(ECHO) Appending installation info to "$(DESTINSTALLARCHLIB)/perllocal.pod" -$(NOECHO) $(MKPATH) "$(DESTINSTALLARCHLIB)" -$(NOECHO) $(DOC_INSTALL) \ "Module" "$(NAME)" \ "installed into" "$(INSTALLPRIVLIB)" \ LINKTYPE "$(LINKTYPE)" \ VERSION "$(VERSION)" \ EXE_FILES "$(EXE_FILES)" \ >> "}.$self->catfile('$(DESTINSTALLARCHLIB)','perllocal.pod').q{" doc_site_install :: all $(NOECHO) $(ECHO) Appending installation info to "$(DESTINSTALLARCHLIB)/perllocal.pod" -$(NOECHO) $(MKPATH) "$(DESTINSTALLARCHLIB)" -$(NOECHO) $(DOC_INSTALL) \ "Module" "$(NAME)" \ "installed into" "$(INSTALLSITELIB)" \ LINKTYPE "$(LINKTYPE)" \ VERSION "$(VERSION)" \ EXE_FILES "$(EXE_FILES)" \ >> "}.$self->catfile('$(DESTINSTALLARCHLIB)','perllocal.pod').q{" doc_vendor_install :: all $(NOECHO) $(ECHO) Appending installation info to "$(DESTINSTALLARCHLIB)/perllocal.pod" -$(NOECHO) $(MKPATH) "$(DESTINSTALLARCHLIB)" -$(NOECHO) $(DOC_INSTALL) \ "Module" "$(NAME)" \ "installed into" "$(INSTALLVENDORLIB)" \ LINKTYPE "$(LINKTYPE)" \ VERSION "$(VERSION)" \ EXE_FILES "$(EXE_FILES)" \ >> "}.$self->catfile('$(DESTINSTALLARCHLIB)','perllocal.pod').q{" } unless $self->{NO_PERLLOCAL}; push @m, q{ uninstall :: uninstall_from_$(INSTALLDIRS)dirs $(NOECHO) $(NOOP) uninstall_from_perldirs :: $(NOECHO) $(UNINSTALL) "}.$self->catfile('$(PERL_ARCHLIB)','auto','$(FULLEXT)','.packlist').q{" uninstall_from_sitedirs :: $(NOECHO) $(UNINSTALL) "}.$self->catfile('$(SITEARCHEXP)','auto','$(FULLEXT)','.packlist').q{" uninstall_from_vendordirs :: $(NOECHO) $(UNINSTALL) "}.$self->catfile('$(VENDORARCHEXP)','auto','$(FULLEXT)','.packlist').q{" }; join("",@m); } sub installbin { my($self) = shift; return "" unless $self->{EXE_FILES} && ref $self->{EXE_FILES} eq "ARRAY"; my @exefiles = sort @{$self->{EXE_FILES}}; return "" unless @exefiles; @exefiles = map vmsify($_), @exefiles if $Is{VMS}; my %fromto; for my $from (@exefiles) { my($path)= $self->catfile('$(INST_SCRIPT)', basename($from)); local($_) = $path; # for backwards compatibility my $to = $self->libscan($path); print "libscan($from) => '$to'\n" if ($Verbose >=2); $to = vmsify($to) if $Is{VMS}; $fromto{$from} = $to; } my @to = sort values %fromto; my @m; push(@m, qq{ EXE_FILES = @exefiles pure_all :: @to \$(NOECHO) \$(NOOP) realclean :: }); # realclean can get rather large. push @m, map "\t$_\n", $self->split_command('$(RM_F)', @to); push @m, "\n"; # A target for each exe file. my @froms = sort keys %fromto; for my $from (@froms) { # 1 2 push @m, _sprintf562 <<'MAKE', $from, $fromto{$from}; %2$s : %1$s $(FIRST_MAKEFILE) $(INST_SCRIPT)$(DFSEP).exists $(INST_BIN)$(DFSEP).exists $(NOECHO) $(RM_F) %2$s $(CP) %1$s %2$s $(FIXIN) %2$s -$(NOECHO) $(CHMOD) $(PERM_RWX) %2$s MAKE } join "", @m; } # LINKTYPE => static or dynamic or '' sub linkext { my($self, %attribs) = @_; my $linktype = $attribs{LINKTYPE}; $linktype = $self->{LINKTYPE} unless defined $linktype; if (defined $linktype and $linktype eq '') { warn "Warning: LINKTYPE set to '', no longer necessary\n"; } $linktype = '$(LINKTYPE)' unless defined $linktype; " linkext :: $linktype \$(NOECHO) \$(NOOP) "; } sub lsdir { # $self my(undef, $dir, $regex) = @_; opendir(my $dh, defined($dir) ? $dir : ".") or return; my @ls = readdir $dh; closedir $dh; @ls = grep(/$regex/, @ls) if defined $regex; @ls; } sub macro { my($self,%attribs) = @_; my @m; foreach my $key (sort keys %attribs) { my $val = $attribs{$key}; push @m, "$key = $val\n"; } join "", @m; } sub makeaperl { my($self, %attribs) = @_; my($makefilename, $searchdirs, $static, $extra, $perlinc, $target, $tmp, $libperl) = @attribs{qw(MAKE DIRS STAT EXTRA INCL TARGET TMP LIBPERL)}; s/^(.*)/"-I$1"/ for @{$perlinc || []}; my(@m); push @m, " # --- MakeMaker makeaperl section --- MAP_TARGET = $target FULLPERL = $self->{FULLPERL} MAP_PERLINC = @{$perlinc || []} "; return join '', @m if $self->{PARENT}; my($dir) = join ":", @{$self->{DIR}}; unless ($self->{MAKEAPERL}) { push @m, q{ $(MAP_TARGET) :: $(MAKE_APERL_FILE) $(MAKE) $(USEMAKEFILE) $(MAKE_APERL_FILE) $@ $(MAKE_APERL_FILE) : static $(FIRST_MAKEFILE) pm_to_blib $(NOECHO) $(ECHO) Writing \"$(MAKE_APERL_FILE)\" for this $(MAP_TARGET) $(NOECHO) $(PERLRUNINST) \ Makefile.PL DIR="}, $dir, q{" \ MAKEFILE=$(MAKE_APERL_FILE) LINKTYPE=static \ MAKEAPERL=1 NORECURS=1 CCCDLFLAGS=}; foreach (@ARGV){ my $arg = $_; # avoid lvalue aliasing if ( $arg =~ /(^.*?=)(.*['\s].*)/ ) { $arg = $1 . $self->quote_literal($2); } push @m, " \\\n\t\t$arg"; } push @m, "\n"; return join '', @m; } my $cccmd = $self->const_cccmd($libperl); $cccmd =~ s/^CCCMD\s*=\s*//; $cccmd =~ s/\$\(INC\)/ "-I$self->{PERL_INC}" /; $cccmd .= " $Config{cccdlflags}" if ($Config{useshrplib} eq 'true'); $cccmd =~ s/\(CC\)/\(PERLMAINCC\)/; # The front matter of the linkcommand... my $linkcmd = join ' ', "\$(CC)", grep($_, @Config{qw(ldflags ccdlflags)}); $linkcmd =~ s/\s+/ /g; $linkcmd =~ s,(perl\.exp),\$(PERL_INC)/$1,; # Which *.a files could we make use of... my $staticlib21 = $self->_find_static_libs($searchdirs); # We trust that what has been handed in as argument, will be buildable $static = [] unless $static; @$staticlib21{@{$static}} = (1) x @{$static}; $extra = [] unless $extra && ref $extra eq 'ARRAY'; for (sort keys %$staticlib21) { next unless /\Q$self->{LIB_EXT}\E\z/; $_ = dirname($_) . "/extralibs.ld"; push @$extra, $_; } s/^(.*)/"-I$1"/ for @{$perlinc || []}; $target ||= "perl"; $tmp ||= "."; # MAP_STATIC doesn't look into subdirs yet. Once "all" is made and we # regenerate the Makefiles, MAP_STATIC and the dependencies for # extralibs.all are computed correctly my @map_static = reverse sort keys %$staticlib21; push @m, " MAP_LINKCMD = $linkcmd MAP_STATIC = ", join(" \\\n\t", map { qq{"$_"} } @map_static), " MAP_STATICDEP = ", join(' ', map { $self->quote_dep($_) } @map_static), " MAP_PRELIBS = $Config{perllibs} $Config{cryptlib} "; my $lperl; if (defined $libperl) { ($lperl = $libperl) =~ s/\$\(A\)/$self->{LIB_EXT}/; } unless ($libperl && -f $lperl) { # Ilya's code... my $dir = $self->{PERL_SRC} || "$self->{PERL_ARCHLIB}/CORE"; $dir = "$self->{PERL_ARCHLIB}/.." if $self->{UNINSTALLED_PERL}; $libperl ||= "libperl$self->{LIB_EXT}"; $libperl = "$dir/$libperl"; $lperl ||= "libperl$self->{LIB_EXT}"; $lperl = "$dir/$lperl"; if (! -f $libperl and ! -f $lperl) { # We did not find a static libperl. Maybe there is a shared one? if ($Is{SunOS}) { $lperl = $libperl = "$dir/$Config{libperl}"; # SUNOS ld does not take the full path to a shared library $libperl = '' if $Is{SunOS4}; } } print <{PERL_SRC}); Warning: $libperl not found If you're going to build a static perl binary, make sure perl is installed otherwise ignore this warning EOF } # SUNOS ld does not take the full path to a shared library my $llibperl = $libperl ? '$(MAP_LIBPERL)' : '-lperl'; my $libperl_dep = $self->quote_dep($libperl); push @m, " MAP_LIBPERL = $libperl MAP_LIBPERLDEP = $libperl_dep LLIBPERL = $llibperl "; push @m, ' $(INST_ARCHAUTODIR)/extralibs.all : $(INST_ARCHAUTODIR)$(DFSEP).exists '.join(" \\\n\t", @$extra).' $(NOECHO) $(RM_F) $@ $(NOECHO) $(TOUCH) $@ '; foreach my $catfile (@$extra){ push @m, "\tcat $catfile >> \$\@\n"; } my $ldfrom = $self->{XSMULTI} ? '' : '$(LDFROM)'; # 1 2 3 4 push @m, _sprintf562 <<'EOF', $tmp, $ldfrom, $self->xs_obj_opt('$@'), $makefilename; $(MAP_TARGET) :: %1$s/perlmain$(OBJ_EXT) $(MAP_LIBPERLDEP) $(MAP_STATICDEP) $(INST_ARCHAUTODIR)/extralibs.all $(MAP_LINKCMD) %2$s $(OPTIMIZE) %1$s/perlmain$(OBJ_EXT) %3$s $(MAP_STATIC) "$(LLIBPERL)" `cat $(INST_ARCHAUTODIR)/extralibs.all` $(MAP_PRELIBS) $(NOECHO) $(ECHO) "To install the new '$(MAP_TARGET)' binary, call" $(NOECHO) $(ECHO) " $(MAKE) $(USEMAKEFILE) %4$s inst_perl MAP_TARGET=$(MAP_TARGET)" $(NOECHO) $(ECHO) " $(MAKE) $(USEMAKEFILE) %4$s map_clean" %1$s/perlmain\$(OBJ_EXT): %1$s/perlmain.c EOF push @m, "\t".$self->cd($tmp, qq[$cccmd "-I\$(PERL_INC)" perlmain.c])."\n"; my $maybe_DynaLoader = $Config{usedl} ? 'q(DynaLoader)' : ''; push @m, _sprintf562 <<'EOF', $tmp, $makefilename, $maybe_DynaLoader; %1$s/perlmain.c: %2$s $(NOECHO) $(ECHO) Writing $@ $(NOECHO) $(PERL) $(MAP_PERLINC) "-MExtUtils::Miniperl" \ -e "writemain(grep(s#.*/auto/##s, @ARGV), %3$s)" $(MAP_STATIC) > $@t $(MV) $@t $@ EOF push @m, "\t", q{$(NOECHO) $(PERL) "$(INSTALLSCRIPT)/fixpmain" } if (defined (&Dos::UseLFN) && Dos::UseLFN()==0); push @m, q{ doc_inst_perl : $(NOECHO) $(ECHO) Appending installation info to "$(DESTINSTALLARCHLIB)/perllocal.pod" -$(NOECHO) $(MKPATH) "$(DESTINSTALLARCHLIB)" -$(NOECHO) $(DOC_INSTALL) \ "Perl binary" "$(MAP_TARGET)" \ MAP_STATIC "$(MAP_STATIC)" \ MAP_EXTRA "`cat $(INST_ARCHAUTODIR)/extralibs.all`" \ MAP_LIBPERL "$(MAP_LIBPERL)" \ >> "}.$self->catfile('$(DESTINSTALLARCHLIB)','perllocal.pod').q{" }; push @m, q{ inst_perl : pure_inst_perl doc_inst_perl pure_inst_perl : $(MAP_TARGET) }.$self->{CP}.q{ $(MAP_TARGET) "}.$self->catfile('$(DESTINSTALLBIN)','$(MAP_TARGET)').q{" clean :: map_clean map_clean : }.$self->{RM_F}.qq{ $tmp/perlmain\$(OBJ_EXT) $tmp/perlmain.c \$(MAP_TARGET) $makefilename \$(INST_ARCHAUTODIR)/extralibs.all }; join '', @m; } # utility method sub _find_static_libs { my ($self, $searchdirs) = @_; # don't use File::Spec here because on Win32 F::F still uses "/" my $installed_version = join('/', 'auto', $self->{FULLEXT}, "$self->{BASEEXT}$self->{LIB_EXT}" ); my %staticlib21; require File::Find; File::Find::find(sub { if ($File::Find::name =~ m{/auto/share\z}) { # in a subdir of auto/share, prune because e.g. # Alien::pkgconfig uses File::ShareDir to put .a files # there. do not want $File::Find::prune = 1; return; } return unless m/\Q$self->{LIB_EXT}\E$/; return unless -f 'extralibs.ld'; # this checks is a "proper" XS installation # Skip perl's libraries. return if m/^libperl/ or m/^perl\Q$self->{LIB_EXT}\E$/; # Skip purified versions of libraries # (e.g., DynaLoader_pure_p1_c0_032.a) return if m/_pure_\w+_\w+_\w+\.\w+$/ and -f "$File::Find::dir/.pure"; if( exists $self->{INCLUDE_EXT} ){ my $found = 0; (my $xx = $File::Find::name) =~ s,.*?/auto/,,s; $xx =~ s,/?$_,,; $xx =~ s,/,::,g; # Throw away anything not explicitly marked for inclusion. # DynaLoader is implied. foreach my $incl ((@{$self->{INCLUDE_EXT}},'DynaLoader')){ if( $xx eq $incl ){ $found++; last; } } return unless $found; } elsif( exists $self->{EXCLUDE_EXT} ){ (my $xx = $File::Find::name) =~ s,.*?/auto/,,s; $xx =~ s,/?$_,,; $xx =~ s,/,::,g; # Throw away anything explicitly marked for exclusion foreach my $excl (@{$self->{EXCLUDE_EXT}}){ return if( $xx eq $excl ); } } # don't include the installed version of this extension. I # leave this line here, although it is not necessary anymore: # I patched minimod.PL instead, so that Miniperl.pm won't # include duplicates # Once the patch to minimod.PL is in the distribution, I can # drop it return if $File::Find::name =~ m:\Q$installed_version\E\z:; return if !$self->xs_static_lib_is_xs($_); use Cwd 'cwd'; $staticlib21{cwd() . "/" . $_}++; }, grep( -d $_, map { $self->catdir($_, 'auto') } @{$searchdirs || []}) ); return \%staticlib21; } sub xs_static_lib_is_xs { my ($self, $libfile) = @_; my $devnull = File::Spec->devnull; return `nm $libfile 2>$devnull` =~ /\bboot_/; } sub makefile { my($self) = shift; my $m; # We do not know what target was originally specified so we # must force a manual rerun to be sure. But as it should only # happen very rarely it is not a significant problem. $m = ' $(OBJECT) : $(FIRST_MAKEFILE) ' if $self->{OBJECT}; my $newer_than_target = $Is{VMS} ? '$(MMS$SOURCE_LIST)' : '$?'; my $mpl_args = join " ", map qq["$_"], @ARGV; my $cross = ''; if (defined $::Cross::platform) { # Inherited from win32/buildext.pl $cross = "-MCross=$::Cross::platform "; } $m .= sprintf <<'MAKE_FRAG', $newer_than_target, $cross, $mpl_args; # We take a very conservative approach here, but it's worth it. # We move Makefile to Makefile.old here to avoid gnu make looping. $(FIRST_MAKEFILE) : Makefile.PL $(CONFIGDEP) $(NOECHO) $(ECHO) "Makefile out-of-date with respect to %s" $(NOECHO) $(ECHO) "Cleaning current config before rebuilding Makefile..." -$(NOECHO) $(RM_F) $(MAKEFILE_OLD) -$(NOECHO) $(MV) $(FIRST_MAKEFILE) $(MAKEFILE_OLD) - $(MAKE) $(USEMAKEFILE) $(MAKEFILE_OLD) clean $(DEV_NULL) $(PERLRUN) %sMakefile.PL %s $(NOECHO) $(ECHO) "==> Your Makefile has been rebuilt. <==" $(NOECHO) $(ECHO) "==> Please rerun the $(MAKE) command. <==" $(FALSE) MAKE_FRAG return $m; } sub maybe_command { my($self,$file) = @_; return $file if -x $file && ! -d $file; return; } sub needs_linking { my($self) = shift; my $caller = (caller(0))[3]; confess("needs_linking called too early") if $caller =~ /^ExtUtils::MakeMaker::/; return $self->{NEEDS_LINKING} if defined $self->{NEEDS_LINKING}; if ($self->has_link_code or $self->{MAKEAPERL}){ $self->{NEEDS_LINKING} = 1; return 1; } foreach my $child (keys %{$self->{CHILDREN}}) { if ($self->{CHILDREN}->{$child}->needs_linking) { $self->{NEEDS_LINKING} = 1; return 1; } } return $self->{NEEDS_LINKING} = 0; } sub parse_abstract { my($self,$parsefile) = @_; my $result; local $/ = "\n"; open(my $fh, '<', $parsefile) or die "Could not open '$parsefile': $!"; binmode $fh; my $inpod = 0; my $pod_encoding; my $package = $self->{DISTNAME}; $package =~ s/-/::/g; while (<$fh>) { $inpod = /^=(?!cut)/ ? 1 : /^=cut/ ? 0 : $inpod; next if !$inpod; s#\r*\n\z##; # handle CRLF input if ( /^=encoding\s*(.*)$/i ) { $pod_encoding = $1; } if ( /^($package(?:\.pm)? \s+ -+ \s+)(.*)/x ) { $result = $2; next; } next unless $result; if ( $result && ( /^\s*$/ || /^\=/ ) ) { last; } $result = join ' ', $result, $_; } close $fh; if ( $pod_encoding and !( $] < 5.008 or !$Config{useperlio} ) ) { # Have to wrap in an eval{} for when running under PERL_CORE # Encode isn't available during build phase and parsing # ABSTRACT isn't important there eval { require Encode; $result = Encode::decode($pod_encoding, $result); } } return $result; } sub parse_version { my($self,$parsefile) = @_; my $result; local $/ = "\n"; local $_; open(my $fh, '<', $parsefile) or die "Could not open '$parsefile': $!"; my $inpod = 0; while (<$fh>) { $inpod = /^=(?!cut)/ ? 1 : /^=cut/ ? 0 : $inpod; next if $inpod || /^\s*#/; chop; next if /^\s*(if|unless|elsif)/; if ( m{^ \s* package \s+ \w[\w\:\']* \s+ (v?[0-9._]+) \s* (;|\{) }x ) { local $^W = 0; $result = $1; } elsif ( m{(?=!])\=[^=]}x ) { $result = $self->get_version($parsefile, $1, $2); } else { next; } last if defined $result; } close $fh; if ( defined $result && $result !~ /^v?[\d_\.]+$/ ) { require version; my $normal = eval { version->new( $result ) }; $result = $normal if defined $normal; } $result = "undef" unless defined $result; return $result; } sub get_version { my ($self, $parsefile, $sigil, $name) = @_; my $line = $_; # from the while() loop in parse_version { package ExtUtils::MakeMaker::_version; undef *version; # in case of unexpected version() sub eval { require version; version::->import; }; no strict; local *{$name}; local $^W = 0; $line = $1 if $line =~ m{^(.+)}s; eval($line); ## no critic return ${$name}; } } sub pasthru { my($self) = shift; my(@m); my(@pasthru); my($sep) = $Is{VMS} ? ',' : ''; $sep .= "\\\n\t"; foreach my $key (qw(LIB LIBPERL_A LINKTYPE OPTIMIZE PREFIX INSTALL_BASE) ) { next unless defined $self->{$key}; push @pasthru, "$key=\"\$($key)\""; } foreach my $key (qw(DEFINE INC)) { # default to the make var my $val = qq{\$($key)}; # expand within perl if given since need to use quote_literal # since INC might include space-protecting ""! chomp($val = $self->{$key}) if defined $self->{$key}; $val .= " \$(PASTHRU_$key)"; my $quoted = $self->quote_literal($val); push @pasthru, qq{PASTHRU_$key=$quoted}; } push @m, "\nPASTHRU = ", join ($sep, @pasthru), "\n"; join "", @m; } sub perl_script { my($self,$file) = @_; return $file if -r $file && -f _; return; } sub perldepend { my($self) = shift; my(@m); my $make_config = $self->cd('$(PERL_SRC)', '$(MAKE) lib/Config.pm'); push @m, sprintf <<'MAKE_FRAG', $make_config if $self->{PERL_SRC}; # Check for unpropogated config.sh changes. Should never happen. # We do NOT just update config.h because that is not sufficient. # An out of date config.h is not fatal but complains loudly! $(PERL_INCDEP)/config.h: $(PERL_SRC)/config.sh -$(NOECHO) $(ECHO) "Warning: $(PERL_INC)/config.h out of date with $(PERL_SRC)/config.sh"; $(FALSE) $(PERL_ARCHLIB)/Config.pm: $(PERL_SRC)/config.sh $(NOECHO) $(ECHO) "Warning: $(PERL_ARCHLIB)/Config.pm may be out of date with $(PERL_SRC)/config.sh" %s MAKE_FRAG return join "", @m unless $self->needs_linking; if ($self->{OBJECT}) { # Need to add an object file dependency on the perl headers. # this is very important for XS modules in perl.git development. push @m, $self->_perl_header_files_fragment("/"); # Directory separator between $(PERL_INC)/header.h } push @m, join(" ", sort values %{$self->{XS}})." : \$(XSUBPPDEPS)\n" if %{$self->{XS}}; return join "\n", @m; } sub pm_to_blib { my $self = shift; my($autodir) = $self->catdir('$(INST_LIB)','auto'); my $r = q{ pm_to_blib : $(FIRST_MAKEFILE) $(TO_INST_PM) }; # VMS will swallow '' and PM_FILTER is often empty. So use q[] my $pm_to_blib = $self->oneliner(<split_command($pm_to_blib, map { ($self->quote_literal($_) => $self->quote_literal($self->{PM}->{$_})) } sort keys %{$self->{PM}}); $r .= join '', map { "\t\$(NOECHO) $_\n" } @cmds; $r .= qq{\t\$(NOECHO) \$(TOUCH) pm_to_blib\n}; return $r; } # transform dot-separated version string into comma-separated quadruple # examples: '1.2.3.4.5' => '1,2,3,4' # '1.2.3' => '1,2,3,0' sub _ppd_version { my ($self, $string) = @_; return join ',', ((split /\./, $string), (0) x 4)[0..3]; } sub ppd { my($self) = @_; my $abstract = $self->{ABSTRACT} || ''; $abstract =~ s/\n/\\n/sg; $abstract =~ s//>/g; my $author = join(', ',@{ ref $self->{AUTHOR} eq 'ARRAY' ? $self->{AUTHOR} : [ $self->{AUTHOR} || '']}); $author =~ s//>/g; my $ppd_file = "$self->{DISTNAME}.ppd"; my @ppd_chunks = qq(\n); push @ppd_chunks, sprintf <<'PPD_HTML', $abstract, $author; %s %s PPD_HTML push @ppd_chunks, " \n"; if ( $self->{MIN_PERL_VERSION} ) { my $min_perl_version = $self->_ppd_version($self->{MIN_PERL_VERSION}); push @ppd_chunks, sprintf <<'PPD_PERLVERS', $min_perl_version; PPD_PERLVERS } # Don't add "perl" to requires. perl dependencies are # handles by ARCHITECTURE. my %prereqs = %{$self->{PREREQ_PM}}; delete $prereqs{perl}; # Build up REQUIRE foreach my $prereq (sort keys %prereqs) { my $name = $prereq; $name .= '::' unless $name =~ /::/; my $version = $prereqs{$prereq}; my %attrs = ( NAME => $name ); $attrs{VERSION} = $version if $version; my $attrs = join " ", map { qq[$_="$attrs{$_}"] } sort keys %attrs; push @ppd_chunks, qq( \n); } my $archname = $Config{archname}; if ($] >= 5.008) { # archname did not change from 5.6 to 5.8, but those versions may # not be not binary compatible so now we append the part of the # version that changes when binary compatibility may change $archname .= "-$Config{PERL_REVISION}.$Config{PERL_VERSION}"; } push @ppd_chunks, sprintf <<'PPD_OUT', $archname; PPD_OUT if ($self->{PPM_INSTALL_SCRIPT}) { if ($self->{PPM_INSTALL_EXEC}) { push @ppd_chunks, sprintf qq{ %s\n}, $self->{PPM_INSTALL_EXEC}, $self->{PPM_INSTALL_SCRIPT}; } else { push @ppd_chunks, sprintf qq{ %s\n}, $self->{PPM_INSTALL_SCRIPT}; } } if ($self->{PPM_UNINSTALL_SCRIPT}) { if ($self->{PPM_UNINSTALL_EXEC}) { push @ppd_chunks, sprintf qq{ %s\n}, $self->{PPM_UNINSTALL_EXEC}, $self->{PPM_UNINSTALL_SCRIPT}; } else { push @ppd_chunks, sprintf qq{ %s\n}, $self->{PPM_UNINSTALL_SCRIPT}; } } my ($bin_location) = $self->{BINARY_LOCATION} || ''; $bin_location =~ s/\\/\\\\/g; push @ppd_chunks, sprintf <<'PPD_XML', $bin_location; PPD_XML my @ppd_cmds = $self->stashmeta(join('', @ppd_chunks), $ppd_file); return sprintf <<'PPD_OUT', join "\n\t", @ppd_cmds; # Creates a PPD (Perl Package Description) for a binary distribution. ppd : %s PPD_OUT } sub prefixify { my($self,$var,$sprefix,$rprefix,$default) = @_; my $path = $self->{uc $var} || $Config_Override{lc $var} || $Config{lc $var} || ''; $rprefix .= '/' if $sprefix =~ m|/$|; warn " prefixify $var => $path\n" if $Verbose >= 2; warn " from $sprefix to $rprefix\n" if $Verbose >= 2; if( $self->{ARGS}{PREFIX} && $path !~ s{^\Q$sprefix\E\b}{$rprefix}s ) { warn " cannot prefix, using default.\n" if $Verbose >= 2; warn " no default!\n" if !$default && $Verbose >= 2; $path = $self->catdir($rprefix, $default) if $default; } print " now $path\n" if $Verbose >= 2; return $self->{uc $var} = $path; } sub processPL { my $self = shift; my $pl_files = $self->{PL_FILES}; return "" unless $pl_files; my $m = ''; foreach my $plfile (sort keys %$pl_files) { my $list = ref($pl_files->{$plfile}) ? $pl_files->{$plfile} : [$pl_files->{$plfile}]; foreach my $target (@$list) { if( $Is{VMS} ) { $plfile = vmsify($self->eliminate_macros($plfile)); $target = vmsify($self->eliminate_macros($target)); } # Normally a .PL file runs AFTER pm_to_blib so it can have # blib in its @INC and load the just built modules. BUT if # the generated module is something in $(TO_INST_PM) which # pm_to_blib depends on then it can't depend on pm_to_blib # else we have a dependency loop. my $pm_dep; my $perlrun; if( defined $self->{PM}{$target} ) { $pm_dep = ''; $perlrun = 'PERLRUN'; } else { $pm_dep = 'pm_to_blib'; $perlrun = 'PERLRUNINST'; } $m .= <quote_literal($_), @cmds; $cmd = $self->escape_newlines($cmd); $switches = join ' ', @$switches; return qq{\$(ABSPERLRUN) $switches -e $cmd --}; } sub quote_literal { my($self, $text, $opts) = @_; $opts->{allow_variables} = 1 unless defined $opts->{allow_variables}; # Quote single quotes $text =~ s{'}{'\\''}g; $text = $opts->{allow_variables} ? $self->escape_dollarsigns($text) : $self->escape_all_dollarsigns($text); return "'$text'"; } sub escape_newlines { my($self, $text) = @_; $text =~ s{\n}{\\\n}g; return $text; } sub max_exec_len { my $self = shift; if (!defined $self->{_MAX_EXEC_LEN}) { if (my $arg_max = eval { require POSIX; &POSIX::ARG_MAX }) { $self->{_MAX_EXEC_LEN} = $arg_max; } else { # POSIX minimum exec size $self->{_MAX_EXEC_LEN} = 4096; } } return $self->{_MAX_EXEC_LEN}; } sub static { # --- Static Loading Sections --- my($self) = shift; ' ## $(INST_PM) has been moved to the all: target. ## It remains here for awhile to allow for old usage: "make static" static :: $(FIRST_MAKEFILE) $(INST_STATIC) $(NOECHO) $(NOOP) '; } sub static_lib { my($self) = @_; return '' unless $self->has_link_code; my(@m); my @libs; if ($self->{XSMULTI}) { for my $ext ($self->_xs_list_basenames) { my ($v, $d, $f) = File::Spec->splitpath($ext); my @d = File::Spec->splitdir($d); shift @d if $d[0] eq 'lib'; my $instdir = $self->catdir('$(INST_ARCHLIB)', 'auto', @d, $f); my $instfile = $self->catfile($instdir, "$f\$(LIB_EXT)"); my $objfile = "$ext\$(OBJ_EXT)"; push @libs, [ $objfile, $instfile, $instdir ]; } } else { @libs = ([ qw($(OBJECT) $(INST_STATIC) $(INST_ARCHAUTODIR)) ]); } push @m, map { $self->xs_make_static_lib(@$_); } @libs; join "\n", @m; } sub xs_make_static_lib { my ($self, $from, $to, $todir) = @_; my @m = sprintf '%s: %s $(MYEXTLIB) %s$(DFSEP).exists'."\n", $to, $from, $todir; push @m, "\t\$(RM_F) \"\$\@\"\n"; push @m, $self->static_lib_fixtures; push @m, $self->static_lib_pure_cmd($from); push @m, "\t\$(CHMOD) \$(PERM_RWX) \$\@\n"; push @m, $self->static_lib_closures($todir); join '', @m; } sub static_lib_closures { my ($self, $todir) = @_; my @m = sprintf <<'MAKE_FRAG', $todir; $(NOECHO) $(ECHO) "$(EXTRALIBS)" > %s$(DFSEP)extralibs.ld MAKE_FRAG # Old mechanism - still available: push @m, <<'MAKE_FRAG' if $self->{PERL_SRC} && $self->{EXTRALIBS}; $(NOECHO) $(ECHO) "$(EXTRALIBS)" >> $(PERL_SRC)$(DFSEP)ext.libs MAKE_FRAG @m; } sub static_lib_fixtures { my ($self) = @_; # If this extension has its own library (eg SDBM_File) # then copy that to $(INST_STATIC) and add $(OBJECT) into it. return unless $self->{MYEXTLIB}; "\t\$(CP) \$(MYEXTLIB) \"\$\@\"\n"; } sub static_lib_pure_cmd { my ($self, $from) = @_; my $ar; if (exists $self->{FULL_AR} && -x $self->{FULL_AR}) { # Prefer the absolute pathed ar if available so that PATH # doesn't confuse us. Perl itself is built with the full_ar. $ar = 'FULL_AR'; } else { $ar = 'AR'; } sprintf <<'MAKE_FRAG', $ar, $from; $(%s) $(AR_STATIC_ARGS) "$@" %s $(RANLIB) "$@" MAKE_FRAG } sub staticmake { my($self, %attribs) = @_; my(@static); my(@searchdirs)=($self->{PERL_ARCHLIB}, $self->{SITEARCHEXP}, $self->{INST_ARCHLIB}); # And as it's not yet built, we add the current extension # but only if it has some C code (or XS code, which implies C code) if (@{$self->{C}}) { @static = $self->catfile($self->{INST_ARCHLIB}, "auto", $self->{FULLEXT}, "$self->{BASEEXT}$self->{LIB_EXT}" ); } # Either we determine now, which libraries we will produce in the # subdirectories or we do it at runtime of the make. # We could ask all subdir objects, but I cannot imagine, why it # would be necessary. # Instead we determine all libraries for the new perl at # runtime. my(@perlinc) = ($self->{INST_ARCHLIB}, $self->{INST_LIB}, $self->{PERL_ARCHLIB}, $self->{PERL_LIB}); $self->makeaperl(MAKE => $self->{MAKEFILE}, DIRS => \@searchdirs, STAT => \@static, INCL => \@perlinc, TARGET => $self->{MAP_TARGET}, TMP => "", LIBPERL => $self->{LIBPERL_A} ); } sub subdir_x { my($self, $subdir) = @_; my $subdir_cmd = $self->cd($subdir, '$(MAKE) $(USEMAKEFILE) $(FIRST_MAKEFILE) all $(PASTHRU)' ); return sprintf <<'EOT', $subdir_cmd; subdirs :: $(NOECHO) %s EOT } sub subdirs { # --- Sub-directory Sections --- my($self) = shift; my(@m); # This method provides a mechanism to automatically deal with # subdirectories containing further Makefile.PL scripts. # It calls the subdir_x() method for each subdirectory. foreach my $dir (@{$self->{DIR}}){ push @m, $self->subdir_x($dir); #### print "Including $dir subdirectory\n"; } if (@m){ unshift @m, <<'EOF'; # The default clean, realclean and test targets in this Makefile # have automatically been given entries for each subdir. EOF } else { push(@m, "\n# none") } join('',@m); } sub test { my($self, %attribs) = @_; my $tests = $attribs{TESTS} || ''; if (!$tests && -d 't' && defined $attribs{RECURSIVE_TEST_FILES}) { $tests = $self->find_tests_recursive; } elsif (!$tests && -d 't') { $tests = $self->find_tests; } # have to do this because nmake is broken $tests =~ s!/!\\!g if $self->is_make_type('nmake'); # note: 'test.pl' name is also hardcoded in init_dirscan() my @m; my $default_testtype = $Config{usedl} ? 'dynamic' : 'static'; push @m, <{SKIPHASH}{$_}, $linktype, "pure_all"; # no depend on a linktype if SKIPped push @m, "subdirs-test_$linktype :: $directdeps\n"; foreach my $dir (@{ $self->{DIR} }) { my $test = $self->cd($dir, "\$(MAKE) test_$linktype \$(PASTHRU)"); push @m, "\t\$(NOECHO) $test\n"; } push @m, "\n"; if ($tests or -f "test.pl") { for my $testspec ([ '', '' ], [ 'db', ' $(TESTDB_SW)' ]) { my ($db, $switch) = @$testspec; my ($command, $deps); # if testdb, build all but don't test all $deps = $db eq 'db' ? $directdeps : "subdirs-test_$linktype"; if ($linktype eq 'static' and $self->needs_linking) { my $target = File::Spec->rel2abs('$(MAP_TARGET)'); $command = qq{"$target" \$(MAP_PERLINC)}; $deps .= ' $(MAP_TARGET)'; } else { $command = '$(FULLPERLRUN)' . $switch; } push @m, "test${db}_$linktype :: $deps\n"; if ($db eq 'db') { push @m, $self->test_via_script($command, '$(TEST_FILE)') } else { push @m, $self->test_via_script($command, '$(TEST_FILE)') if -f "test.pl"; push @m, $self->test_via_harness($command, '$(TEST_FILES)') if $tests; } push @m, "\n"; } } else { push @m, _sprintf562 <<'EOF', $linktype; testdb_%1$s test_%1$s :: subdirs-test_%1$s $(NOECHO) $(ECHO) 'No tests defined for $(NAME) extension.' EOF } } join "", @m; } sub test_via_harness { my($self, $perl, $tests) = @_; return $self->SUPER::test_via_harness("PERL_DL_NONLAZY=1 $perl", $tests); } sub test_via_script { my($self, $perl, $script) = @_; return $self->SUPER::test_via_script("PERL_DL_NONLAZY=1 $perl", $script); } sub tool_xsubpp { my($self) = shift; return "" unless $self->needs_linking; my $xsdir; my @xsubpp_dirs = @INC; # Make sure we pick up the new xsubpp if we're building perl. unshift @xsubpp_dirs, $self->{PERL_LIB} if $self->{PERL_CORE}; my $foundxsubpp = 0; foreach my $dir (@xsubpp_dirs) { $xsdir = $self->catdir($dir, 'ExtUtils'); if( -r $self->catfile($xsdir, "xsubpp") ) { $foundxsubpp = 1; last; } } die "ExtUtils::MM_Unix::tool_xsubpp : Can't find xsubpp" if !$foundxsubpp; my $tmdir = $self->catdir($self->{PERL_LIB},"ExtUtils"); my(@tmdeps) = $self->catfile($tmdir,'typemap'); if( $self->{TYPEMAPS} ){ foreach my $typemap (@{$self->{TYPEMAPS}}){ if( ! -f $typemap ) { warn "Typemap $typemap not found.\n"; } else { $typemap = vmsify($typemap) if $Is{VMS}; push(@tmdeps, $typemap); } } } push(@tmdeps, "typemap") if -f "typemap"; # absolutised because with deep-located typemaps, eg "lib/XS/typemap", # if xsubpp is called from top level with # $(XSUBPP) ... -typemap "lib/XS/typemap" "lib/XS/Test.xs" # it says: # Can't find lib/XS/type map in (fulldir)/lib/XS # because ExtUtils::ParseXS::process_file chdir's to .xs file's # location. This is the only way to get all specified typemaps used, # wherever located. my @tmargs = map { '-typemap '.$self->quote_literal(File::Spec->rel2abs($_)) } @tmdeps; $_ = $self->quote_dep($_) for @tmdeps; if( exists $self->{XSOPT} ){ unshift( @tmargs, $self->{XSOPT} ); } if ($Is{VMS} && $Config{'ldflags'} && $Config{'ldflags'} =~ m!/Debug!i && (!exists($self->{XSOPT}) || $self->{XSOPT} !~ /linenumbers/) ) { unshift(@tmargs,'-nolinenumbers'); } $self->{XSPROTOARG} = "" unless defined $self->{XSPROTOARG}; my $xsdirdep = $self->quote_dep($xsdir); # -dep for use when dependency not command return qq{ XSUBPPDIR = $xsdir XSUBPP = "\$(XSUBPPDIR)\$(DFSEP)xsubpp" XSUBPPRUN = \$(PERLRUN) \$(XSUBPP) XSPROTOARG = $self->{XSPROTOARG} XSUBPPDEPS = @tmdeps $xsdirdep\$(DFSEP)xsubpp XSUBPPARGS = @tmargs XSUBPP_EXTRA_ARGS = }; } sub all_target { my $self = shift; return <<'MAKE_EXT'; all :: pure_all manifypods $(NOECHO) $(NOOP) MAKE_EXT } sub top_targets { # --- Target Sections --- my($self) = shift; my(@m); push @m, $self->all_target, "\n" unless $self->{SKIPHASH}{'all'}; push @m, sprintf <<'EOF'; pure_all :: config pm_to_blib subdirs linkext $(NOECHO) $(NOOP) $(NOECHO) $(NOOP) subdirs :: $(MYEXTLIB) $(NOECHO) $(NOOP) config :: $(FIRST_MAKEFILE) blibdirs $(NOECHO) $(NOOP) EOF push @m, ' $(O_FILES) : $(H_FILES) ' if @{$self->{O_FILES} || []} && @{$self->{H} || []}; push @m, q{ help : perldoc ExtUtils::MakeMaker }; join('',@m); } sub writedoc { # --- perllocal.pod section --- my($self,$what,$name,@attribs)=@_; my $time = gmtime($ENV{SOURCE_DATE_EPOCH} || time); print "=head2 $time: $what C<$name>\n\n=over 4\n\n=item *\n\n"; print join "\n\n=item *\n\n", map("C<$_>",@attribs); print "\n\n=back\n\n"; } sub xs_c { my($self) = shift; return '' unless $self->needs_linking(); ' .xs.c: $(XSUBPPRUN) $(XSPROTOARG) $(XSUBPPARGS) $(XSUBPP_EXTRA_ARGS) $*.xs > $*.xsc $(MV) $*.xsc $*.c '; } sub xs_cpp { my($self) = shift; return '' unless $self->needs_linking(); ' .xs.cpp: $(XSUBPPRUN) $(XSPROTOARG) $(XSUBPPARGS) $*.xs > $*.xsc $(MV) $*.xsc $*.cpp '; } sub xs_o { my ($self) = @_; return '' unless $self->needs_linking(); my $m_o = $self->{XSMULTI} ? $self->xs_obj_opt('$*$(OBJ_EXT)') : ''; my $frag = ''; # dmake makes noise about ambiguous rule $frag .= sprintf <<'EOF', $m_o unless $self->is_make_type('dmake'); .xs$(OBJ_EXT) : $(XSUBPPRUN) $(XSPROTOARG) $(XSUBPPARGS) $*.xs > $*.xsc $(MV) $*.xsc $*.c $(CCCMD) $(CCCDLFLAGS) "-I$(PERL_INC)" $(PASTHRU_DEFINE) $(DEFINE) $*.c %s EOF if ($self->{XSMULTI}) { for my $ext ($self->_xs_list_basenames) { my $pmfile = "$ext.pm"; croak "$ext.xs has no matching $pmfile: $!" unless -f $pmfile; my $version = $self->parse_version($pmfile); my $cccmd = $self->{CONST_CCCMD}; $cccmd =~ s/^\s*CCCMD\s*=\s*//; $cccmd =~ s/\$\(DEFINE_VERSION\)/-DVERSION=\\"$version\\"/; $cccmd =~ s/\$\(XS_DEFINE_VERSION\)/-DXS_VERSION=\\"$version\\"/; $self->_xsbuild_replace_macro($cccmd, 'xs', $ext, 'INC'); my $define = '$(DEFINE)'; $self->_xsbuild_replace_macro($define, 'xs', $ext, 'DEFINE'); # 1 2 3 4 $frag .= _sprintf562 <<'EOF', $ext, $cccmd, $m_o, $define; %1$s$(OBJ_EXT): %1$s.xs $(XSUBPPRUN) $(XSPROTOARG) $(XSUBPPARGS) $*.xs > $*.xsc $(MV) $*.xsc $*.c %2$s $(CCCDLFLAGS) "-I$(PERL_INC)" $(PASTHRU_DEFINE) %4$s $*.c %3$s EOF } } $frag; } # param gets modified sub _xsbuild_replace_macro { my ($self, undef, $xstype, $ext, $varname) = @_; my $value = $self->_xsbuild_value($xstype, $ext, $varname); return unless defined $value; $_[1] =~ s/\$\($varname\)/$value/; } sub _xsbuild_value { my ($self, $xstype, $ext, $varname) = @_; return $self->{XSBUILD}{$xstype}{$ext}{$varname} if $self->{XSBUILD}{$xstype}{$ext}{$varname}; return $self->{XSBUILD}{$xstype}{all}{$varname} if $self->{XSBUILD}{$xstype}{all}{$varname}; (); } 1; __END__ package ExtUtils::MM; use strict; use ExtUtils::MakeMaker::Config; our $VERSION = '7.34'; $VERSION = eval $VERSION; require ExtUtils::Liblist; require ExtUtils::MakeMaker; our @ISA = qw(ExtUtils::Liblist ExtUtils::MakeMaker); { # Convenient alias. package MM; our @ISA = qw(ExtUtils::MM); sub DESTROY {} } sub _is_win95 { # miniperl might not have the Win32 functions available and we need # to run in miniperl. my $have_win32 = eval { require Win32 }; return $have_win32 && defined &Win32::IsWin95 ? Win32::IsWin95() : ! defined $ENV{SYSTEMROOT}; } my %Is = (); $Is{VMS} = $^O eq 'VMS'; $Is{OS2} = $^O eq 'os2'; $Is{MacOS} = $^O eq 'MacOS'; if( $^O eq 'MSWin32' ) { _is_win95() ? $Is{Win95} = 1 : $Is{Win32} = 1; } $Is{UWIN} = $^O =~ /^uwin(-nt)?$/; $Is{Cygwin} = $^O eq 'cygwin'; $Is{NW5} = $Config{osname} eq 'NetWare'; # intentional $Is{BeOS} = ($^O =~ /beos/i or $^O eq 'haiku'); $Is{DOS} = $^O eq 'dos'; if( $Is{NW5} ) { $^O = 'NetWare'; delete $Is{Win32}; } $Is{VOS} = $^O eq 'vos'; $Is{QNX} = $^O eq 'qnx'; $Is{AIX} = $^O eq 'aix'; $Is{Darwin} = $^O eq 'darwin'; $Is{Unix} = !grep { $_ } values %Is; map { delete $Is{$_} unless $Is{$_} } keys %Is; _assert( keys %Is == 1 ); my($OS) = keys %Is; my $class = "ExtUtils::MM_$OS"; eval "require $class" unless $INC{"ExtUtils/MM_$OS.pm"}; ## no critic die $@ if $@; unshift @ISA, $class; sub _assert { my $sanity = shift; die sprintf "Assert failed at %s line %d\n", (caller)[1,2] unless $sanity; return; } #--------------------------------------------------------------------------# # This is a modified copy of version.pm 0.9909, bundled exclusively for # use by ExtUtils::Makemaker and its dependencies to bootstrap when # version.pm is not available. It should not be used by ordinary modules. # # When loaded, it will try to load version.pm. If that fails, it will load # ExtUtils::MakeMaker::version::vpp and alias various *version functions # to functions in that module. It will also override UNIVERSAL::VERSION. #--------------------------------------------------------------------------# package ExtUtils::MakeMaker::version; use 5.006001; use strict; use vars qw(@ISA $VERSION $CLASS $STRICT $LAX *declare *qv); $VERSION = '7.34'; $VERSION = eval $VERSION; $CLASS = 'version'; { local $SIG{'__DIE__'}; eval "use version"; if ( $@ ) { # don't have any version.pm installed eval "use ExtUtils::MakeMaker::version::vpp"; die "$@" if ( $@ ); local $^W; delete $INC{'version.pm'}; $INC{'version.pm'} = $INC{'ExtUtils/MakeMaker/version.pm'}; push @version::ISA, "ExtUtils::MakeMaker::version::vpp"; $version::VERSION = $VERSION; *version::qv = \&ExtUtils::MakeMaker::version::vpp::qv; *version::declare = \&ExtUtils::MakeMaker::version::vpp::declare; *version::_VERSION = \&ExtUtils::MakeMaker::version::vpp::_VERSION; *version::vcmp = \&ExtUtils::MakeMaker::version::vpp::vcmp; *version::new = \&ExtUtils::MakeMaker::version::vpp::new; if ($] >= 5.009000) { no strict 'refs'; *version::stringify = \&ExtUtils::MakeMaker::version::vpp::stringify; *{'version::(""'} = \&ExtUtils::MakeMaker::version::vpp::stringify; *{'version::(<=>'} = \&ExtUtils::MakeMaker::version::vpp::vcmp; *version::parse = \&ExtUtils::MakeMaker::version::vpp::parse; } require ExtUtils::MakeMaker::version::regex; *version::is_lax = \&ExtUtils::MakeMaker::version::regex::is_lax; *version::is_strict = \&ExtUtils::MakeMaker::version::regex::is_strict; *LAX = \$ExtUtils::MakeMaker::version::regex::LAX; *STRICT = \$ExtUtils::MakeMaker::version::regex::STRICT; } elsif ( ! version->can('is_qv') ) { *version::is_qv = sub { exists $_[0]->{qv} }; } } 1; package ExtUtils::MakeMaker::Config; use strict; our $VERSION = '7.34'; $VERSION = eval $VERSION; use Config (); # Give us an overridable config. our %Config = %Config::Config; sub import { my $caller = caller; no strict 'refs'; ## no critic *{$caller.'::Config'} = \%Config; } 1; package ExtUtils::MakeMaker::Locale; use strict; our $VERSION = "7.34"; $VERSION = eval $VERSION; use base 'Exporter'; our @EXPORT_OK = qw( decode_argv env $ENCODING_LOCALE $ENCODING_LOCALE_FS $ENCODING_CONSOLE_IN $ENCODING_CONSOLE_OUT ); use Encode (); use Encode::Alias (); our $ENCODING_LOCALE; our $ENCODING_LOCALE_FS; our $ENCODING_CONSOLE_IN; our $ENCODING_CONSOLE_OUT; sub DEBUG () { 0 } sub _init { if ($^O eq "MSWin32") { unless ($ENCODING_LOCALE) { # Try to obtain what the Windows ANSI code page is eval { unless (defined &GetConsoleCP) { require Win32; # manually "import" it since Win32->import refuses *GetConsoleCP = sub { &Win32::GetConsoleCP } if defined &Win32::GetConsoleCP; } unless (defined &GetConsoleCP) { require Win32::API; Win32::API->Import('kernel32', 'int GetConsoleCP()'); } if (defined &GetConsoleCP) { my $cp = GetConsoleCP(); $ENCODING_LOCALE = "cp$cp" if $cp; } }; } unless ($ENCODING_CONSOLE_IN) { # only test one since set together unless (defined &GetInputCP) { eval { require Win32; eval { Win32::GetConsoleCP() }; # manually "import" it since Win32->import refuses *GetInputCP = sub { &Win32::GetConsoleCP } if defined &Win32::GetConsoleCP; *GetOutputCP = sub { &Win32::GetConsoleOutputCP } if defined &Win32::GetConsoleOutputCP; }; unless (defined &GetInputCP) { eval { # try Win32::Console module for codepage to use require Win32::Console; *GetInputCP = sub { &Win32::Console::InputCP } if defined &Win32::Console::InputCP; *GetOutputCP = sub { &Win32::Console::OutputCP } if defined &Win32::Console::OutputCP; }; } unless (defined &GetInputCP) { # final fallback *GetInputCP = *GetOutputCP = sub { # another fallback that could work is: # reg query HKLM\System\CurrentControlSet\Control\Nls\CodePage /v ACP ((qx(chcp) || '') =~ /^Active code page: (\d+)/) ? $1 : (); }; } } my $cp = GetInputCP(); $ENCODING_CONSOLE_IN = "cp$cp" if $cp; $cp = GetOutputCP(); $ENCODING_CONSOLE_OUT = "cp$cp" if $cp; } } unless ($ENCODING_LOCALE) { eval { require I18N::Langinfo; $ENCODING_LOCALE = I18N::Langinfo::langinfo(I18N::Langinfo::CODESET()); # Workaround of Encode < v2.25. The "646" encoding alias was # introduced in Encode-2.25, but we don't want to require that version # quite yet. Should avoid the CPAN testers failure reported from # openbsd-4.7/perl-5.10.0 combo. $ENCODING_LOCALE = "ascii" if $ENCODING_LOCALE eq "646"; # https://rt.cpan.org/Ticket/Display.html?id=66373 $ENCODING_LOCALE = "hp-roman8" if $^O eq "hpux" && $ENCODING_LOCALE eq "roman8"; }; $ENCODING_LOCALE ||= $ENCODING_CONSOLE_IN; } if ($^O eq "darwin") { $ENCODING_LOCALE_FS ||= "UTF-8"; } # final fallback $ENCODING_LOCALE ||= $^O eq "MSWin32" ? "cp1252" : "UTF-8"; $ENCODING_LOCALE_FS ||= $ENCODING_LOCALE; $ENCODING_CONSOLE_IN ||= $ENCODING_LOCALE; $ENCODING_CONSOLE_OUT ||= $ENCODING_CONSOLE_IN; unless (Encode::find_encoding($ENCODING_LOCALE)) { my $foundit; if (lc($ENCODING_LOCALE) eq "gb18030") { eval { require Encode::HanExtra; }; if ($@) { die "Need Encode::HanExtra to be installed to support locale codeset ($ENCODING_LOCALE), stopped"; } $foundit++ if Encode::find_encoding($ENCODING_LOCALE); } die "The locale codeset ($ENCODING_LOCALE) isn't one that perl can decode, stopped" unless $foundit; } # use Data::Dump; ddx $ENCODING_LOCALE, $ENCODING_LOCALE_FS, $ENCODING_CONSOLE_IN, $ENCODING_CONSOLE_OUT; } _init(); Encode::Alias::define_alias(sub { no strict 'refs'; no warnings 'once'; return ${"ENCODING_" . uc(shift)}; }, "locale"); sub _flush_aliases { no strict 'refs'; for my $a (sort keys %Encode::Alias::Alias) { if (defined ${"ENCODING_" . uc($a)}) { delete $Encode::Alias::Alias{$a}; warn "Flushed alias cache for $a" if DEBUG; } } } sub reinit { $ENCODING_LOCALE = shift; $ENCODING_LOCALE_FS = shift; $ENCODING_CONSOLE_IN = $ENCODING_LOCALE; $ENCODING_CONSOLE_OUT = $ENCODING_LOCALE; _init(); _flush_aliases(); } sub decode_argv { die if defined wantarray; for (@ARGV) { $_ = Encode::decode(locale => $_, @_); } } sub env { my $k = Encode::encode(locale => shift); my $old = $ENV{$k}; if (@_) { my $v = shift; if (defined $v) { $ENV{$k} = Encode::encode(locale => $v); } else { delete $ENV{$k}; } } return Encode::decode(locale => $old) if defined wantarray; } 1; __END__ package ExtUtils::Constant::Utils; use strict; use vars qw($VERSION @EXPORT_OK @ISA); use Carp; @ISA = 'Exporter'; @EXPORT_OK = qw(C_stringify perl_stringify); $VERSION = '0.04'; use constant is_perl55 => ($] < 5.005_50); use constant is_perl56 => ($] < 5.007 && $] > 5.005_50); use constant is_sane_perl => $] > 5.007; # Hopefully make a happy C identifier. sub C_stringify { local $_ = shift; return unless defined $_; # grr 5.6.1 confess "Wide character in '$_' intended as a C identifier" if tr/\0-\377// != length; # grr 5.6.1 more so because its regexps will break on data that happens to # be utf8, which includes my 8 bit test cases. $_ = pack 'C*', unpack 'U*', $_ . pack 'U*' if is_perl56; s/\\/\\\\/g; s/([\"\'])/\\$1/g; # Grr. fix perl mode. s/\n/\\n/g; # Ensure newlines don't end up in octal s/\r/\\r/g; s/\t/\\t/g; s/\f/\\f/g; s/\a/\\a/g; unless (is_perl55) { # This will elicit a warning on 5.005_03 about [: :] being reserved unless # I cheat my $cheat = '([[:^print:]])'; if (ord('A') == 193) { # EBCDIC has no ^\0-\177 workalike. s/$cheat/sprintf "\\%03o", ord $1/ge; } else { s/([^\0-\177])/sprintf "\\%03o", ord $1/ge; } s/$cheat/sprintf "\\%03o", ord $1/ge; } else { require POSIX; s/([^A-Za-z0-9_])/POSIX::isprint($1) ? $1 : sprintf "\\%03o", ord $1/ge; } $_; } # Hopefully make a happy perl identifier. sub perl_stringify { local $_ = shift; return unless defined $_; s/\\/\\\\/g; s/([\"\'])/\\$1/g; # Grr. fix perl mode. s/\n/\\n/g; # Ensure newlines don't end up in octal s/\r/\\r/g; s/\t/\\t/g; s/\f/\\f/g; s/\a/\\a/g; unless (is_perl55) { # This will elicit a warning on 5.005_03 about [: :] being reserved unless # I cheat my $cheat = '([[:^print:]])'; if (is_sane_perl) { if (ord('A') == 193) { # EBCDIC has no ^\0-\177 workalike. s/$cheat/sprintf "\\x{%X}", ord $1/ge; } else { s/([^\0-\177])/sprintf "\\x{%X}", ord $1/ge; } } else { # Grr 5.6.1. And I don't think I can use utf8; to force the regexp # because 5.005_03 will fail. # This is grim, but I also can't split on // my $copy; foreach my $index (0 .. length ($_) - 1) { my $char = substr ($_, $index, 1); $copy .= ($char le "\177") ? $char : sprintf "\\x{%X}", ord $char; } $_ = $copy; } s/$cheat/sprintf "\\%03o", ord $1/ge; } else { # Turns out "\x{}" notation only arrived with 5.6 s/([^\0-\177])/sprintf "\\x%02X", ord $1/ge; require POSIX; s/([^A-Za-z0-9_])/POSIX::isprint($1) ? $1 : sprintf "\\%03o", ord $1/ge; } $_; } 1; __END__ package ExtUtils::Constant::ProxySubs; use strict; use vars qw($VERSION @ISA %type_to_struct %type_from_struct %type_to_sv %type_to_C_value %type_is_a_problem %type_num_args %type_temporary); use Carp; require ExtUtils::Constant::XS; use ExtUtils::Constant::Utils qw(C_stringify); use ExtUtils::Constant::XS qw(%XS_TypeSet); $VERSION = '0.09'; @ISA = 'ExtUtils::Constant::XS'; %type_to_struct = ( IV => '{const char *name; I32 namelen; IV value;}', NV => '{const char *name; I32 namelen; NV value;}', UV => '{const char *name; I32 namelen; UV value;}', PV => '{const char *name; I32 namelen; const char *value;}', PVN => '{const char *name; I32 namelen; const char *value; STRLEN len;}', YES => '{const char *name; I32 namelen;}', NO => '{const char *name; I32 namelen;}', UNDEF => '{const char *name; I32 namelen;}', '' => '{const char *name; I32 namelen;} ', ); %type_from_struct = ( IV => sub { $_[0] . '->value' }, NV => sub { $_[0] . '->value' }, UV => sub { $_[0] . '->value' }, PV => sub { $_[0] . '->value' }, PVN => sub { $_[0] . '->value', $_[0] . '->len' }, YES => sub {}, NO => sub {}, UNDEF => sub {}, '' => sub {}, ); %type_to_sv = ( IV => sub { "newSViv($_[0])" }, NV => sub { "newSVnv($_[0])" }, UV => sub { "newSVuv($_[0])" }, PV => sub { "newSVpv($_[0], 0)" }, PVN => sub { "newSVpvn($_[0], $_[1])" }, YES => sub { '&PL_sv_yes' }, NO => sub { '&PL_sv_no' }, UNDEF => sub { '&PL_sv_undef' }, '' => sub { '&PL_sv_yes' }, SV => sub {"SvREFCNT_inc($_[0])"}, ); %type_to_C_value = ( YES => sub {}, NO => sub {}, UNDEF => sub {}, '' => sub {}, ); sub type_to_C_value { my ($self, $type) = @_; return $type_to_C_value{$type} || sub {return map {ref $_ ? @$_ : $_} @_}; } # TODO - figure out if there is a clean way for the type_to_sv code to # attempt s/sv_2mortal// and if it succeeds tell type_to_sv not to add # SvREFCNT_inc %type_is_a_problem = ( # The documentation says *mortal SV*, but we now need a non-mortal copy. SV => 1, ); %type_temporary = ( SV => ['SV *'], PV => ['const char *'], PVN => ['const char *', 'STRLEN'], ); $type_temporary{$_} = [$_] foreach qw(IV UV NV); while (my ($type, $value) = each %XS_TypeSet) { $type_num_args{$type} = defined $value ? ref $value ? scalar @$value : 1 : 0; } $type_num_args{''} = 0; sub partition_names { my ($self, $default_type, @items) = @_; my (%found, @notfound, @trouble); while (my $item = shift @items) { my $default = delete $item->{default}; if ($default) { # If we find a default value, convert it into a regular item and # append it to the queue of items to process my $default_item = {%$item}; $default_item->{invert_macro} = 1; $default_item->{pre} = delete $item->{def_pre}; $default_item->{post} = delete $item->{def_post}; $default_item->{type} = shift @$default; $default_item->{value} = $default; push @items, $default_item; } else { # It can be "not found" unless it's the default (invert the macro) # or the "macro" is an empty string (ie no macro) push @notfound, $item unless $item->{invert_macro} or !$self->macro_to_ifdef($self->macro_from_item($item)); } if ($item->{pre} or $item->{post} or $item->{not_constant} or $type_is_a_problem{$item->{type}}) { push @trouble, $item; } else { push @{$found{$item->{type}}}, $item; } } # use Data::Dumper; print Dumper \%found; (\%found, \@notfound, \@trouble); } sub boottime_iterator { my ($self, $type, $iterator, $hash, $subname, $push) = @_; my $extractor = $type_from_struct{$type}; die "Can't find extractor code for type $type" unless defined $extractor; my $generator = $type_to_sv{$type}; die "Can't find generator code for type $type" unless defined $generator; my $athx = $self->C_constant_prefix_param(); if ($push) { return sprintf <<"EOBOOT", &$generator(&$extractor($iterator)); while ($iterator->name) { he = $subname($athx $hash, $iterator->name, $iterator->namelen, %s); av_push(push, newSVhek(HeKEY_hek(he))); ++$iterator; } EOBOOT } else { return sprintf <<"EOBOOT", &$generator(&$extractor($iterator)); while ($iterator->name) { $subname($athx $hash, $iterator->name, $iterator->namelen, %s); ++$iterator; } EOBOOT } } sub name_len_value_macro { my ($self, $item) = @_; my $name = $item->{name}; my $value = $item->{value}; $value = $item->{name} unless defined $value; my $namelen = length $name; if ($name =~ tr/\0-\377// != $namelen) { # the hash API signals UTF-8 by passing the length negated. utf8::encode($name); $namelen = -length $name; } $name = C_stringify($name); my $macro = $self->macro_from_item($item); ($name, $namelen, $value, $macro); } sub WriteConstants { my $self = shift; my $ARGS = {@_}; my ($c_fh, $xs_fh, $c_subname, $default_type, $package) = @{$ARGS}{qw(C_FH XS_FH C_SUBNAME DEFAULT_TYPE NAME)}; my $xs_subname = exists $ARGS->{XS_SUBNAME} ? $ARGS->{XS_SUBNAME} : 'constant'; my $options = $ARGS->{PROXYSUBS}; $options = {} unless ref $options; my $push = $options->{push}; my $explosives = $options->{croak_on_read}; my $croak_on_error = $options->{croak_on_error}; my $autoload = $options->{autoload}; { my $exclusive = 0; ++$exclusive if $explosives; ++$exclusive if $croak_on_error; ++$exclusive if $autoload; # Until someone patches this (with test cases): carp ("PROXYSUBS options 'autoload', 'croak_on_read' and 'croak_on_error' cannot be used together") if $exclusive > 1; } # Strictly it requires Perl_caller_cx carp ("PROXYSUBS option 'croak_on_error' requires v5.13.5 or later") if $croak_on_error && $^V < v5.13.5; # Strictly this is actually 5.8.9, but it's not well tested there my $can_do_pcs = $] >= 5.009; # Until someone patches this (with test cases) carp ("PROXYSUBS option 'push' requires v5.10 or later") if $push && !$can_do_pcs; # Until someone patches this (with test cases) carp ("PROXYSUBS options 'push' and 'croak_on_read' cannot be used together") if $explosives && $push; # If anyone is insane enough to suggest a package name containing % my $package_sprintf_safe = $package; $package_sprintf_safe =~ s/%/%%/g; # All the types we see my $what = {}; # A hash to lookup items with. my $items = {}; my @items = $self->normalise_items ({disable_utf8_duplication => 1}, $default_type, $what, $items, @{$ARGS->{NAMES}}); # Partition the values by type. Also include any defaults in here # Everything that doesn't have a default needs alternative code for # "I'm missing" # And everything that has pre or post code ends up in a private block my ($found, $notfound, $trouble) = $self->partition_names($default_type, @items); my $pthx = $self->C_constant_prefix_param_defintion(); my $athx = $self->C_constant_prefix_param(); my $symbol_table = C_stringify($package) . '::'; $push = C_stringify($package . '::' . $push) if $push; my $cast_CONSTSUB = $] < 5.010 ? '(char *)' : ''; print $c_fh $self->header(); if ($autoload || $croak_on_error) { print $c_fh <<'EOC'; /* This allows slightly more efficient code on !USE_ITHREADS: */ #ifdef USE_ITHREADS # define COP_FILE(c) CopFILE(c) # define COP_FILE_F "s" #else # define COP_FILE(c) CopFILESV(c) # define COP_FILE_F SVf #endif EOC } my $return_type = $push ? 'HE *' : 'void'; print $c_fh <<"EOADD"; static $return_type ${c_subname}_add_symbol($pthx HV *hash, const char *name, I32 namelen, SV *value) { EOADD if (!$can_do_pcs) { print $c_fh <<'EO_NOPCS'; if (namelen == namelen) { EO_NOPCS } else { print $c_fh <<"EO_PCS"; HE *he = (HE*) hv_common_key_len(hash, name, namelen, HV_FETCH_LVALUE, NULL, 0); SV *sv; if (!he) { croak("Couldn't add key '%s' to %%$package_sprintf_safe\::", name); } sv = HeVAL(he); if (SvOK(sv) || SvTYPE(sv) == SVt_PVGV) { /* Someone has been here before us - have to make a real sub. */ EO_PCS } # This piece of code is common to both print $c_fh <<"EOADD"; newCONSTSUB(hash, ${cast_CONSTSUB}name, value); EOADD if ($can_do_pcs) { print $c_fh <<'EO_PCS'; } else { SvUPGRADE(sv, SVt_RV); SvRV_set(sv, value); SvROK_on(sv); SvREADONLY_on(value); } EO_PCS } else { print $c_fh <<'EO_NOPCS'; } EO_NOPCS } print $c_fh " return he;\n" if $push; print $c_fh <<'EOADD'; } EOADD print $c_fh $explosives ? <<"EXPLODE" : "\n"; static int Im_sorry_Dave(pTHX_ SV *sv, MAGIC *mg) { PERL_UNUSED_ARG(mg); croak("Your vendor has not defined $package_sprintf_safe macro %"SVf " used", sv); NORETURN_FUNCTION_END; } static MGVTBL not_defined_vtbl = { Im_sorry_Dave, /* get - I'm afraid I can't do that */ Im_sorry_Dave, /* set */ 0, /* len */ 0, /* clear */ 0, /* free */ 0, /* copy */ 0, /* dup */ }; EXPLODE { my $key = $symbol_table; # Just seems tidier (and slightly more space efficient) not to have keys # such as Fcntl:: $key =~ s/::$//; my $key_len = length $key; print $c_fh <<"MISSING"; #ifndef SYMBIAN /* Store a hash of all symbols missing from the package. To avoid trampling on the package namespace (uninvited) put each package's hash in our namespace. To avoid creating lots of typeblogs and symbol tables for sub-packages, put each package's hash into one hash in our namespace. */ static HV * get_missing_hash(pTHX) { HV *const parent = get_hv("ExtUtils::Constant::ProxySubs::Missing", GVf_MULTI); /* We could make a hash of hashes directly, but this would confuse anything at Perl space that looks at us, and as we're visible in Perl space, best to play nice. */ SV *const *const ref = hv_fetch(parent, "$key", $key_len, TRUE); HV *new_hv; if (!ref) return NULL; if (SvROK(*ref)) return (HV*) SvRV(*ref); new_hv = newHV(); SvUPGRADE(*ref, SVt_RV); SvRV_set(*ref, (SV *)new_hv); SvROK_on(*ref); return new_hv; } #endif MISSING } print $xs_fh <<"EOBOOT"; BOOT: { #if defined(dTHX) && !defined(PERL_NO_GET_CONTEXT) dTHX; #endif HV *symbol_table = get_hv("$symbol_table", GV_ADD); EOBOOT if ($push) { print $xs_fh <<"EOC"; AV *push = get_av(\"$push\", GV_ADD); HE *he; EOC } my %iterator; $found->{''} = [map {{%$_, type=>'', invert_macro => 1}} @$notfound]; foreach my $type (sort keys %$found) { my $struct = $type_to_struct{$type}; my $type_to_value = $self->type_to_C_value($type); my $number_of_args = $type_num_args{$type}; die "Can't find structure definition for type $type" unless defined $struct; my $lc_type = $type ? lc($type) : 'notfound'; my $struct_type = $lc_type . '_s'; my $array_name = 'values_for_' . $lc_type; $iterator{$type} = 'value_for_' . $lc_type; # Give the notfound struct file scope. The others are scoped within the # BOOT block my $struct_fh = $type ? $xs_fh : $c_fh; print $c_fh "struct $struct_type $struct;\n"; print $struct_fh <<"EOBOOT"; static const struct $struct_type $array_name\[] = { EOBOOT foreach my $item (@{$found->{$type}}) { my ($name, $namelen, $value, $macro) = $self->name_len_value_macro($item); my $ifdef = $self->macro_to_ifdef($macro); if (!$ifdef && $item->{invert_macro}) { carp("Attempting to supply a default for '$name' which has no conditional macro"); next; } if ($item->{invert_macro}) { print $struct_fh $self->macro_to_ifndef($macro); print $struct_fh " /* This is the default value: */\n" if $type; } else { print $struct_fh $ifdef; } print $struct_fh " { ", join (', ', "\"$name\"", $namelen, &$type_to_value($value)), " },\n", $self->macro_to_endif($macro); } # Terminate the list with a NULL print $struct_fh " { NULL, 0", (", 0" x $number_of_args), " } };\n"; print $xs_fh <<"EOBOOT" if $type; const struct $struct_type *$iterator{$type} = $array_name; EOBOOT } delete $found->{''}; my $add_symbol_subname = $c_subname . '_add_symbol'; foreach my $type (sort keys %$found) { print $xs_fh $self->boottime_iterator($type, $iterator{$type}, 'symbol_table', $add_symbol_subname, $push); } print $xs_fh <<"EOBOOT"; if (C_ARRAY_LENGTH(values_for_notfound) > 1) { #ifndef SYMBIAN HV *const ${c_subname}_missing = get_missing_hash(aTHX); #endif const struct notfound_s *value_for_notfound = values_for_notfound; do { EOBOOT print $xs_fh $explosives ? <<"EXPLODE" : << "DONT"; SV *tripwire = newSV(0); sv_magicext(tripwire, 0, PERL_MAGIC_ext, ¬_defined_vtbl, 0, 0); SvPV_set(tripwire, (char *)value_for_notfound->name); if(value_for_notfound->namelen >= 0) { SvCUR_set(tripwire, value_for_notfound->namelen); } else { SvCUR_set(tripwire, -value_for_notfound->namelen); SvUTF8_on(tripwire); } SvPOKp_on(tripwire); SvREADONLY_on(tripwire); assert(SvLEN(tripwire) == 0); $add_symbol_subname($athx symbol_table, value_for_notfound->name, value_for_notfound->namelen, tripwire); EXPLODE /* Need to add prototypes, else parsing will vary by platform. */ HE *he = (HE*) hv_common_key_len(symbol_table, value_for_notfound->name, value_for_notfound->namelen, HV_FETCH_LVALUE, NULL, 0); SV *sv; #ifndef SYMBIAN HEK *hek; #endif if (!he) { croak("Couldn't add key '%s' to %%$package_sprintf_safe\::", value_for_notfound->name); } sv = HeVAL(he); if (!SvOK(sv) && SvTYPE(sv) != SVt_PVGV) { /* Nothing was here before, so mark a prototype of "" */ sv_setpvn(sv, "", 0); } else if (SvPOK(sv) && SvCUR(sv) == 0) { /* There is already a prototype of "" - do nothing */ } else { /* Someone has been here before us - have to make a real typeglob. */ /* It turns out to be incredibly hard to deal with all the corner cases of sub foo (); and reporting errors correctly, so lets cheat a bit. Start with a constant subroutine */ CV *cv = newCONSTSUB(symbol_table, ${cast_CONSTSUB}value_for_notfound->name, &PL_sv_yes); /* and then turn it into a non constant declaration only. */ SvREFCNT_dec(CvXSUBANY(cv).any_ptr); CvCONST_off(cv); CvXSUB(cv) = NULL; CvXSUBANY(cv).any_ptr = NULL; } #ifndef SYMBIAN hek = HeKEY_hek(he); if (!hv_common(${c_subname}_missing, NULL, HEK_KEY(hek), HEK_LEN(hek), HEK_FLAGS(hek), HV_FETCH_ISSTORE, &PL_sv_yes, HEK_HASH(hek))) croak("Couldn't add key '%s' to missing_hash", value_for_notfound->name); #endif DONT print $xs_fh " av_push(push, newSVhek(hek));\n" if $push; print $xs_fh <<"EOBOOT"; } while ((++value_for_notfound)->name); } EOBOOT foreach my $item (@$trouble) { my ($name, $namelen, $value, $macro) = $self->name_len_value_macro($item); my $ifdef = $self->macro_to_ifdef($macro); my $type = $item->{type}; my $type_to_value = $self->type_to_C_value($type); print $xs_fh $ifdef; if ($item->{invert_macro}) { print $xs_fh " /* This is the default value: */\n" if $type; print $xs_fh "#else\n"; } my $generator = $type_to_sv{$type}; die "Can't find generator code for type $type" unless defined $generator; print $xs_fh " {\n"; # We need to use a temporary value because some really troublesome # items use C pre processor directives in their values, and in turn # these don't fit nicely in the macro-ised generator functions my $counter = 0; printf $xs_fh " %s temp%d;\n", $_, $counter++ foreach @{$type_temporary{$type}}; print $xs_fh " $item->{pre}\n" if $item->{pre}; # And because the code in pre might be both declarations and # statements, we can't declare and assign to the temporaries in one. $counter = 0; printf $xs_fh " temp%d = %s;\n", $counter++, $_ foreach &$type_to_value($value); my @tempvarnames = map {sprintf 'temp%d', $_} 0 .. $counter - 1; printf $xs_fh <<"EOBOOT", $name, &$generator(@tempvarnames); ${c_subname}_add_symbol($athx symbol_table, "%s", $namelen, %s); EOBOOT print $xs_fh " $item->{post}\n" if $item->{post}; print $xs_fh " }\n"; print $xs_fh $self->macro_to_endif($macro); } if ($] >= 5.009) { print $xs_fh <blk_oldcop : PL_curcop; EOC void AUTOLOAD() PROTOTYPE: DISABLE PREINIT: SV *sv = newSVpvn_flags(SvPVX(cv), SvCUR(cv), SVs_TEMP | SvUTF8(cv)); const COP *cop = PL_curcop; EOA print $xs_fh <<"EOC"; PPCODE: #ifndef SYMBIAN /* It's not obvious how to calculate this at C pre-processor time. However, any compiler optimiser worth its salt should be able to remove the dead code, and hopefully the now-obviously-unused static function too. */ HV *${c_subname}_missing = (C_ARRAY_LENGTH(values_for_notfound) > 1) ? get_missing_hash(aTHX) : NULL; if ((C_ARRAY_LENGTH(values_for_notfound) > 1) ? hv_exists_ent(${c_subname}_missing, sv, 0) : 0) { sv = newSVpvf("Your vendor has not defined $package_sprintf_safe macro %" SVf ", used at %" COP_FILE_F " line %" UVuf "\\n", sv, COP_FILE(cop), (UV)CopLINE(cop)); } else #endif { sv = newSVpvf("%" SVf " is not a valid $package_sprintf_safe macro at %" COP_FILE_F " line %" UVuf "\\n", sv, COP_FILE(cop), (UV)CopLINE(cop)); } croak_sv(sv_2mortal(sv)); EOC } else { print $xs_fh $explosives ? <<"EXPLODE" : <<"DONT"; void $xs_subname(sv) INPUT: SV * sv; PPCODE: sv = newSVpvf("Your vendor has not defined $package_sprintf_safe macro %" SVf ", used", sv); PUSHs(sv_2mortal(sv)); EXPLODE void $xs_subname(sv) INPUT: SV * sv; PPCODE: #ifndef SYMBIAN /* It's not obvious how to calculate this at C pre-processor time. However, any compiler optimiser worth its salt should be able to remove the dead code, and hopefully the now-obviously-unused static function too. */ HV *${c_subname}_missing = (C_ARRAY_LENGTH(values_for_notfound) > 1) ? get_missing_hash(aTHX) : NULL; if ((C_ARRAY_LENGTH(values_for_notfound) > 1) ? hv_exists_ent(${c_subname}_missing, sv, 0) : 0) { sv = newSVpvf("Your vendor has not defined $package_sprintf_safe macro %" SVf ", used", sv); } else #endif { sv = newSVpvf("%" SVf " is not a valid $package_sprintf_safe macro", sv); } PUSHs(sv_2mortal(sv)); DONT } } 1; package ExtUtils::Constant::Base; use strict; use vars qw($VERSION); use Carp; use Text::Wrap; use ExtUtils::Constant::Utils qw(C_stringify perl_stringify); $VERSION = '0.06'; use constant is_perl56 => ($] < 5.007 && $] > 5.005_50); sub valid_type { # Default to assuming that you don't need different types of return data. 1; } sub default_type { ''; } sub header { '' } # This might actually be a return statement. Note that you are responsible # for any space you might need before your value, as it lets to perform # "tricks" such as "return KEY_" and have strings appended. sub assignment_clause_for_type; # In which case this might be an empty string sub return_statement_for_type {undef}; sub return_statement_for_notdef; sub return_statement_for_notfound; # "#if 1" is true to a C pre-processor sub macro_from_name { 1; } sub macro_from_item { 1; } sub macro_to_ifdef { my ($self, $macro) = @_; if (ref $macro) { return $macro->[0]; } if (defined $macro && $macro ne "" && $macro ne "1") { return $macro ? "#ifdef $macro\n" : "#if 0\n"; } return ""; } sub macro_to_ifndef { my ($self, $macro) = @_; if (ref $macro) { # Can't invert these stylishly, so "bodge it" return "$macro->[0]#else\n"; } if (defined $macro && $macro ne "" && $macro ne "1") { return $macro ? "#ifndef $macro\n" : "#if 1\n"; } croak "Can't generate an ifndef for unconditional code"; } sub macro_to_endif { my ($self, $macro) = @_; if (ref $macro) { return $macro->[1]; } if (defined $macro && $macro ne "" && $macro ne "1") { return "#endif\n"; } return ""; } sub name_param { 'name'; } # This is possibly buggy, in that it's not mandatory (below, in the main # C_constant parameters, but is expected to exist here, if it's needed) # Buggy because if you're definitely pure 8 bit only, and will never be # presented with your constants in utf8, the default form of C_constant can't # be told not to do the utf8 version. sub is_utf8_param { 'utf8'; } sub memEQ { "!memcmp"; } sub memEQ_clause { # if (memEQ(name, "thingy", 6)) { # Which could actually be a character comparison or even "" my ($self, $args) = @_; my ($name, $checked_at, $indent) = @{$args}{qw(name checked_at indent)}; $indent = ' ' x ($indent || 4); my $front_chop; if (ref $checked_at) { # regexp won't work on 5.6.1 without use utf8; in turn that won't work # on 5.005_03. substr ($name, 0, length $$checked_at,) = ''; $front_chop = C_stringify ($$checked_at); undef $checked_at; } my $len = length $name; if ($len < 2) { return $indent . "{\n" if (defined $checked_at and $checked_at == 0) or $len == 0; # We didn't switch, drop through to the code for the 2 character string $checked_at = 1; } my $name_param = $self->name_param; if ($len < 3 and defined $checked_at) { my $check; if ($checked_at == 1) { $check = 0; } elsif ($checked_at == 0) { $check = 1; } if (defined $check) { my $char = C_stringify (substr $name, $check, 1); # Placate 5.005 with a break in the string. I can't see a good way of # getting it to not take [ as introducing an array lookup, even with # ${name_param}[$check] return $indent . "if ($name_param" . "[$check] == '$char') {\n"; } } if (($len == 2 and !defined $checked_at) or ($len == 3 and defined ($checked_at) and $checked_at == 2)) { my $char1 = C_stringify (substr $name, 0, 1); my $char2 = C_stringify (substr $name, 1, 1); return $indent . "if ($name_param" . "[0] == '$char1' && $name_param" . "[1] == '$char2') {\n"; } if (($len == 3 and defined ($checked_at) and $checked_at == 1)) { my $char1 = C_stringify (substr $name, 0, 1); my $char2 = C_stringify (substr $name, 2, 1); return $indent . "if ($name_param" . "[0] == '$char1' && $name_param" . "[2] == '$char2') {\n"; } my $pointer = '^'; my $have_checked_last = defined ($checked_at) && $len == $checked_at + 1; if ($have_checked_last) { # Checked at the last character, so no need to memEQ it. $pointer = C_stringify (chop $name); $len--; } $name = C_stringify ($name); my $memEQ = $self->memEQ(); my $body = $indent . "if ($memEQ($name_param, \"$name\", $len)) {\n"; # Put a little ^ under the letter we checked at # Screws up for non printable and non-7 bit stuff, but that's too hard to # get right. if (defined $checked_at) { $body .= $indent . "/* " . (' ' x length $memEQ) . (' ' x length $name_param) . (' ' x $checked_at) . $pointer . (' ' x ($len - $checked_at + length $len)) . " */\n"; } elsif (defined $front_chop) { $body .= $indent . "/* $front_chop" . (' ' x ($len + 1 + length $len)) . " */\n"; } return $body; } sub dump_names { my ($self, $args, @items) = @_; my ($default_type, $what, $indent, $declare_types) = @{$args}{qw(default_type what indent declare_types)}; $indent = ' ' x ($indent || 0); my $result; my (@simple, @complex, %used_types); foreach (@items) { my $type; if (ref $_) { $type = $_->{type} || $default_type; if ($_->{utf8}) { # For simplicity always skip the bytes case, and reconstitute this entry # from its utf8 twin. next if $_->{utf8} eq 'no'; # Copy the hashref, as we don't want to mess with the caller's hashref. $_ = {%$_}; unless (is_perl56) { utf8::decode ($_->{name}); } else { $_->{name} = pack 'U*', unpack 'U0U*', $_->{name}; } delete $_->{utf8}; } } else { $_ = {name=>$_}; $type = $default_type; } $used_types{$type}++; if ($type eq $default_type # grr 5.6.1 and length $_->{name} and length $_->{name} == ($_->{name} =~ tr/A-Za-z0-9_//) and !defined ($_->{macro}) and !defined ($_->{value}) and !defined ($_->{default}) and !defined ($_->{pre}) and !defined ($_->{post}) and !defined ($_->{def_pre}) and !defined ($_->{def_post}) and !defined ($_->{weight})) { # It's the default type, and the name consists only of A-Za-z0-9_ push @simple, $_->{name}; } else { push @complex, $_; } } if (!defined $declare_types) { # Do they pass in any types we weren't already using? foreach (keys %$what) { next if $used_types{$_}; $declare_types++; # Found one in $what that wasn't used. last; # And one is enough to terminate this loop } } if ($declare_types) { $result = $indent . 'my $types = {map {($_, 1)} qw(' . join (" ", sort keys %$what) . ")};\n"; } local $Text::Wrap::huge = 'overflow'; local $Text::Wrap::columns = 80; $result .= wrap ($indent . "my \@names = (qw(", $indent . " ", join (" ", sort @simple) . ")"); if (@complex) { foreach my $item (sort {$a->{name} cmp $b->{name}} @complex) { my $name = perl_stringify $item->{name}; my $line = ",\n$indent {name=>\"$name\""; $line .= ", type=>\"$item->{type}\"" if defined $item->{type}; foreach my $thing (qw (macro value default pre post def_pre def_post)) { my $value = $item->{$thing}; if (defined $value) { if (ref $value) { $line .= ", $thing=>[\"" . join ('", "', map {perl_stringify $_} @$value) . '"]'; } else { $line .= ", $thing=>\"" . perl_stringify($value) . "\""; } } } $line .= "}"; # Ensure that the enclosing C comment doesn't end # by turning */ into *" . "/ $line =~ s!\*\/!\*" . "/!gs; # gcc -Wall doesn't like finding /* inside a comment $line =~ s!\/\*!/" . "\*!gs; $result .= $line; } } $result .= ");\n"; $result; } # Hmm. value undef to do NOTDEF? value () to do NOTFOUND? sub assign { my $self = shift; my $args = shift; my ($indent, $type, $pre, $post, $item) = @{$args}{qw(indent type pre post item)}; $post ||= ''; my $clause; my $close; if ($pre) { chomp $pre; $close = "$indent}\n"; $clause = $indent . "{\n"; $indent .= " "; $clause .= "$indent$pre"; $clause .= ";" unless $pre =~ /;$/; $clause .= "\n"; } confess "undef \$type" unless defined $type; confess "Can't generate code for type $type" unless $self->valid_type($type); $clause .= join '', map {"$indent$_\n"} $self->assignment_clause_for_type({type=>$type,item=>$item}, @_); chomp $post; if (length $post) { $clause .= "$post"; $clause .= ";" unless $post =~ /;$/; $clause .= "\n"; } my $return = $self->return_statement_for_type($type); $clause .= "$indent$return\n" if defined $return; $clause .= $close if $close; return $clause; } sub return_clause { ##ifdef thingy # *iv_return = thingy; # return PERL_constant_ISIV; ##else # return PERL_constant_NOTDEF; ##endif my ($self, $args, $item) = @_; my $indent = $args->{indent}; my ($name, $value, $default, $pre, $post, $def_pre, $def_post, $type) = @$item{qw (name value default pre post def_pre def_post type)}; $value = $name unless defined $value; my $macro = $self->macro_from_item($item); $indent = ' ' x ($indent || 6); unless (defined $type) { # use Data::Dumper; print STDERR Dumper ($item); confess "undef \$type"; } ##ifdef thingy my $clause = $self->macro_to_ifdef($macro); # *iv_return = thingy; # return PERL_constant_ISIV; $clause .= $self->assign ({indent=>$indent, type=>$type, pre=>$pre, post=>$post, item=>$item}, ref $value ? @$value : $value); if (defined $macro && $macro ne "" && $macro ne "1") { ##else $clause .= "#else\n"; # return PERL_constant_NOTDEF; if (!defined $default) { my $notdef = $self->return_statement_for_notdef(); $clause .= "$indent$notdef\n" if defined $notdef; } else { my @default = ref $default ? @$default : $default; $type = shift @default; $clause .= $self->assign ({indent=>$indent, type=>$type, pre=>$pre, post=>$post, item=>$item}, @default); } } ##endif $clause .= $self->macro_to_endif($macro); return $clause; } sub match_clause { # $offset defined if we have checked an offset. my ($self, $args, $item) = @_; my ($offset, $indent) = @{$args}{qw(checked_at indent)}; $indent = ' ' x ($indent || 4); my $body = ''; my ($no, $yes, $either, $name, $inner_indent); if (ref $item eq 'ARRAY') { ($yes, $no) = @$item; $either = $yes || $no; confess "$item is $either expecting hashref in [0] || [1]" unless ref $either eq 'HASH'; $name = $either->{name}; } else { confess "$item->{name} has utf8 flag '$item->{utf8}', should be false" if $item->{utf8}; $name = $item->{name}; $inner_indent = $indent; } $body .= $self->memEQ_clause ({name => $name, checked_at => $offset, indent => length $indent}); # If we've been presented with an arrayref for $item, then the user string # contains in the range 128-255, and we need to check whether it was utf8 # (or not). # In the worst case we have two named constants, where one's name happens # encoded in UTF8 happens to be the same byte sequence as the second's # encoded in (say) ISO-8859-1. # In this case, $yes and $no both have item hashrefs. if ($yes) { $body .= $indent . " if (" . $self->is_utf8_param . ") {\n"; } elsif ($no) { $body .= $indent . " if (!" . $self->is_utf8_param . ") {\n"; } if ($either) { $body .= $self->return_clause ({indent=>4 + length $indent}, $either); if ($yes and $no) { $body .= $indent . " } else {\n"; $body .= $self->return_clause ({indent=>4 + length $indent}, $no); } $body .= $indent . " }\n"; } else { $body .= $self->return_clause ({indent=>2 + length $indent}, $item); } $body .= $indent . "}\n"; } sub switch_clause { my ($self, $args, $namelen, $items, @items) = @_; my ($indent, $comment) = @{$args}{qw(indent comment)}; $indent = ' ' x ($indent || 2); local $Text::Wrap::huge = 'overflow'; local $Text::Wrap::columns = 80; my @names = sort map {$_->{name}} @items; my $leader = $indent . '/* '; my $follower = ' ' x length $leader; my $body = $indent . "/* Names all of length $namelen. */\n"; if (defined $comment) { $body = wrap ($leader, $follower, $comment) . "\n"; $leader = $follower; } my @safe_names = @names; foreach (@safe_names) { confess sprintf "Name '$_' is length %d, not $namelen", length unless length == $namelen; # Argh. 5.6.1 # next unless tr/A-Za-z0-9_//c; next if tr/A-Za-z0-9_// == length; $_ = '"' . perl_stringify ($_) . '"'; # Ensure that the enclosing C comment doesn't end # by turning */ into *" . "/ s!\*\/!\*"."/!gs; # gcc -Wall doesn't like finding /* inside a comment s!\/\*!/"."\*!gs; } $body .= wrap ($leader, $follower, join (" ", @safe_names) . " */") . "\n"; # Figure out what to switch on. # (RMS, Spread of jump table, Position, Hashref) my @best = (1e38, ~0); # Prefer the last character over the others. (As it lets us shorten the # memEQ clause at no cost). foreach my $i ($namelen - 1, 0 .. ($namelen - 2)) { my ($min, $max) = (~0, 0); my %spread; if (is_perl56) { # Need proper Unicode preserving hash keys for bytes in range 128-255 # here too, for some reason. grr 5.6.1 yet again. tie %spread, 'ExtUtils::Constant::Aaargh56Hash'; } foreach (@names) { my $char = substr $_, $i, 1; my $ord = ord $char; confess "char $ord is out of range" if $ord > 255; $max = $ord if $ord > $max; $min = $ord if $ord < $min; push @{$spread{$char}}, $_; # warn "$_ $char"; } # I'm going to pick the character to split on that minimises the root # mean square of the number of names in each case. Normally this should # be the one with the most keys, but it may pick a 7 where the 8 has # one long linear search. I'm not sure if RMS or just sum of squares is # actually better. # $max and $min are for the tie-breaker if the root mean squares match. # Assuming that the compiler may be building a jump table for the # switch() then try to minimise the size of that jump table. # Finally use < not <= so that if it still ties the earliest part of # the string wins. Because if that passes but the memEQ fails, it may # only need the start of the string to bin the choice. # I think. But I'm micro-optimising. :-) # OK. Trump that. Now favour the last character of the string, before the # rest. my $ss; $ss += @$_ * @$_ foreach values %spread; my $rms = sqrt ($ss / keys %spread); if ($rms < $best[0] || ($rms == $best[0] && ($max - $min) < $best[1])) { @best = ($rms, $max - $min, $i, \%spread); } } confess "Internal error. Failed to pick a switch point for @names" unless defined $best[2]; # use Data::Dumper; print Dumper (@best); my ($offset, $best) = @best[2,3]; $body .= $indent . "/* Offset $offset gives the best switch position. */\n"; my $do_front_chop = $offset == 0 && $namelen > 2; if ($do_front_chop) { $body .= $indent . "switch (*" . $self->name_param() . "++) {\n"; } else { $body .= $indent . "switch (" . $self->name_param() . "[$offset]) {\n"; } foreach my $char (sort keys %$best) { confess sprintf "'$char' is %d bytes long, not 1", length $char if length ($char) != 1; confess sprintf "char %#X is out of range", ord $char if ord ($char) > 255; $body .= $indent . "case '" . C_stringify ($char) . "':\n"; foreach my $thisone (sort { # Deal with the case of an item actually being an array ref to 1 or 2 # hashrefs. Don't assign to $a or $b, as they're aliases to the # original my $l = ref $a eq 'ARRAY' ? ($a->[0] || $->[1]) : $a; my $r = ref $b eq 'ARRAY' ? ($b->[0] || $->[1]) : $b; # Sort by weight first ($r->{weight} || 0) <=> ($l->{weight} || 0) # Sort equal weights by name or $l->{name} cmp $r->{name}} # If this looks evil, maybe it is. $items is a # hashref, and we're doing a hash slice on it @{$items}{@{$best->{$char}}}) { # warn "You are here"; if ($do_front_chop) { $body .= $self->match_clause ({indent => 2 + length $indent, checked_at => \$char}, $thisone); } else { $body .= $self->match_clause ({indent => 2 + length $indent, checked_at => $offset}, $thisone); } } $body .= $indent . " break;\n"; } $body .= $indent . "}\n"; return $body; } sub C_constant_return_type { "static int"; } sub C_constant_prefix_param { ''; } sub C_constant_prefix_param_defintion { ''; } sub name_param_definition { "const char *" . $_[0]->name_param; } sub namelen_param { 'len'; } sub namelen_param_definition { 'size_t ' . $_[0]->namelen_param; } sub C_constant_other_params { ''; } sub C_constant_other_params_defintion { ''; } sub params { ''; } sub dogfood { '' } sub normalise_items { my $self = shift; my $args = shift; my $default_type = shift; my $what = shift; my $items = shift; my @new_items; foreach my $orig (@_) { my ($name, $item); if (ref $orig) { # Make a copy which is a normalised version of the ref passed in. $name = $orig->{name}; my ($type, $macro, $value) = @$orig{qw (type macro value)}; $type ||= $default_type; $what->{$type} = 1; $item = {name=>$name, type=>$type}; undef $macro if defined $macro and $macro eq $name; $item->{macro} = $macro if defined $macro; undef $value if defined $value and $value eq $name; $item->{value} = $value if defined $value; foreach my $key (qw(default pre post def_pre def_post weight not_constant)) { my $value = $orig->{$key}; $item->{$key} = $value if defined $value; # warn "$key $value"; } } else { $name = $orig; $item = {name=>$name, type=>$default_type}; $what->{$default_type} = 1; } warn +(ref ($self) || $self) . "doesn't know how to handle values of type $_ used in macro $name" unless $self->valid_type ($item->{type}); # tr///c is broken on 5.6.1 for utf8, so my original tr/\0-\177//c # doesn't work. Upgrade to 5.8 # if ($name !~ tr/\0-\177//c || $] < 5.005_50) { if ($name =~ tr/\0-\177// == length $name || $] < 5.005_50 || $args->{disable_utf8_duplication}) { # No characters outside 7 bit ASCII. if (exists $items->{$name}) { die "Multiple definitions for macro $name"; } $items->{$name} = $item; } else { # No characters outside 8 bit. This is hardest. if (exists $items->{$name} and ref $items->{$name} ne 'ARRAY') { confess "Unexpected ASCII definition for macro $name"; } # Again, 5.6.1 tr broken, so s/5\.6.*/5\.8\.0/; # if ($name !~ tr/\0-\377//c) { if ($name =~ tr/\0-\377// == length $name) { # if ($] < 5.007) { # $name = pack "C*", unpack "U*", $name; # } $item->{utf8} = 'no'; $items->{$name}[1] = $item; push @new_items, $item; # Copy item, to create the utf8 variant. $item = {%$item}; } # Encode the name as utf8 bytes. unless (is_perl56) { utf8::encode($name); } else { # warn "Was >$name< " . length ${name}; $name = pack 'C*', unpack 'C*', $name . pack 'U*'; # warn "Now '${name}' " . length ${name}; } if ($items->{$name}[0]) { die "Multiple definitions for macro $name"; } $item->{utf8} = 'yes'; $item->{name} = $name; $items->{$name}[0] = $item; # We have need for the utf8 flag. $what->{''} = 1; } push @new_items, $item; } @new_items; } # The parameter now BREAKOUT was previously documented as: # # I if defined signals that all the Is of the Is are of # this length, and that the constant name passed in by perl is checked and # also of this length. It is used during recursion, and should be C # unless the caller has checked all the lengths during code generation, and # the generated subroutine is only to be called with a name of this length. # # As you can see it now performs this function during recursion by being a # scalar reference. sub C_constant { my ($self, $args, @items) = @_; my ($package, $subname, $default_type, $what, $indent, $breakout) = @{$args}{qw(package subname default_type types indent breakout)}; $package ||= 'Foo'; $subname ||= 'constant'; # I'm not using this. But a hashref could be used for full formatting without # breaking this API # $indent ||= 0; my ($namelen, $items); if (ref $breakout) { # We are called recursively. We trust @items to be normalised, $what to # be a hashref, and pinch %$items from our parent to save recalculation. ($namelen, $items) = @$breakout; } else { $items = {}; if (is_perl56) { # Need proper Unicode preserving hash keys. require ExtUtils::Constant::Aaargh56Hash; tie %$items, 'ExtUtils::Constant::Aaargh56Hash'; } $breakout ||= 3; $default_type ||= $self->default_type(); if (!ref $what) { # Convert line of the form IV,UV,NV to hash $what = {map {$_ => 1} split /,\s*/, ($what || '')}; # Figure out what types we're dealing with, and assign all unknowns to the # default type } @items = $self->normalise_items ({}, $default_type, $what, $items, @items); # use Data::Dumper; print Dumper @items; } my $params = $self->params ($what); # Probably "static int" my ($body, @subs); $body = $self->C_constant_return_type($params) . "\n$subname (" # Eg "pTHX_ " . $self->C_constant_prefix_param_defintion($params) # Probably "const char *name" . $self->name_param_definition($params); # Something like ", STRLEN len" $body .= ", " . $self->namelen_param_definition($params) unless defined $namelen; $body .= $self->C_constant_other_params_defintion($params); $body .= ") {\n"; if (defined $namelen) { # We are a child subroutine. Print the simple description my $comment = 'When generated this function returned values for the list' . ' of names given here. However, subsequent manual editing may have' . ' added or removed some.'; $body .= $self->switch_clause ({indent=>2, comment=>$comment}, $namelen, $items, @items); } else { # We are the top level. $body .= " /* Initially switch on the length of the name. */\n"; $body .= $self->dogfood ({package => $package, subname => $subname, default_type => $default_type, what => $what, indent => $indent, breakout => $breakout}, @items); $body .= ' switch ('.$self->namelen_param().") {\n"; # Need to group names of the same length my @by_length; foreach (@items) { push @{$by_length[length $_->{name}]}, $_; } foreach my $i (0 .. $#by_length) { next unless $by_length[$i]; # None of this length $body .= " case $i:\n"; if (@{$by_length[$i]} == 1) { my $only_thing = $by_length[$i]->[0]; if ($only_thing->{utf8}) { if ($only_thing->{utf8} eq 'yes') { # With utf8 on flag item is passed in element 0 $body .= $self->match_clause (undef, [$only_thing]); } else { # With utf8 off flag item is passed in element 1 $body .= $self->match_clause (undef, [undef, $only_thing]); } } else { $body .= $self->match_clause (undef, $only_thing); } } elsif (@{$by_length[$i]} < $breakout) { $body .= $self->switch_clause ({indent=>4}, $i, $items, @{$by_length[$i]}); } else { # Only use the minimal set of parameters actually needed by the types # of the names of this length. my $what = {}; foreach (@{$by_length[$i]}) { $what->{$_->{type}} = 1; $what->{''} = 1 if $_->{utf8}; } $params = $self->params ($what); push @subs, $self->C_constant ({package=>$package, subname=>"${subname}_$i", default_type => $default_type, types => $what, indent => $indent, breakout => [$i, $items]}, @{$by_length[$i]}); $body .= " return ${subname}_$i (" # Eg "aTHX_ " . $self->C_constant_prefix_param($params) # Probably "name" . $self->name_param($params); $body .= $self->C_constant_other_params($params); $body .= ");\n"; } $body .= " break;\n"; } $body .= " }\n"; } my $notfound = $self->return_statement_for_notfound(); $body .= " $notfound\n" if $notfound; $body .= "}\n"; return (@subs, $body); } 1; __END__ package ExtUtils::Constant::XS; use strict; use vars qw($VERSION %XS_Constant %XS_TypeSet @ISA @EXPORT_OK $is_perl56); use Carp; use ExtUtils::Constant::Utils 'perl_stringify'; require ExtUtils::Constant::Base; @ISA = qw(ExtUtils::Constant::Base Exporter); @EXPORT_OK = qw(%XS_Constant %XS_TypeSet); $VERSION = '0.03'; $is_perl56 = ($] < 5.007 && $] > 5.005_50); # '' is used as a flag to indicate non-ascii macro names, and hence the need # to pass in the utf8 on/off flag. %XS_Constant = ( '' => '', IV => 'PUSHi(iv)', UV => 'PUSHu((UV)iv)', NV => 'PUSHn(nv)', PV => 'PUSHp(pv, strlen(pv))', PVN => 'PUSHp(pv, iv)', SV => 'PUSHs(sv)', YES => 'PUSHs(&PL_sv_yes)', NO => 'PUSHs(&PL_sv_no)', UNDEF => '', # implicit undef ); %XS_TypeSet = ( IV => '*iv_return = ', UV => '*iv_return = (IV)', NV => '*nv_return = ', PV => '*pv_return = ', PVN => ['*pv_return = ', '*iv_return = (IV)'], SV => '*sv_return = ', YES => undef, NO => undef, UNDEF => undef, ); sub header { my $start = 1; my @lines; push @lines, "#define PERL_constant_NOTFOUND\t$start\n"; $start++; push @lines, "#define PERL_constant_NOTDEF\t$start\n"; $start++; foreach (sort keys %XS_Constant) { next if $_ eq ''; push @lines, "#define PERL_constant_IS$_\t$start\n"; $start++; } push @lines, << 'EOT'; #ifndef NVTYPE typedef double NV; /* 5.6 and later define NVTYPE, and typedef NV to it. */ #endif #ifndef aTHX_ #define aTHX_ /* 5.6 or later define this for threading support. */ #endif #ifndef pTHX_ #define pTHX_ /* 5.6 or later define this for threading support. */ #endif EOT return join '', @lines; } sub valid_type { my ($self, $type) = @_; return exists $XS_TypeSet{$type}; } # This might actually be a return statement sub assignment_clause_for_type { my $self = shift; my $args = shift; my $type = $args->{type}; my $typeset = $XS_TypeSet{$type}; if (ref $typeset) { die "Type $type is aggregate, but only single value given" if @_ == 1; return map {"$typeset->[$_]$_[$_];"} 0 .. $#$typeset; } elsif (defined $typeset) { confess "Aggregate value given for type $type" if @_ > 1; return "$typeset$_[0];"; } return (); } sub return_statement_for_type { my ($self, $type) = @_; # In the future may pass in an options hash $type = $type->{type} if ref $type; "return PERL_constant_IS$type;"; } sub return_statement_for_notdef { # my ($self) = @_; "return PERL_constant_NOTDEF;"; } sub return_statement_for_notfound { # my ($self) = @_; "return PERL_constant_NOTFOUND;"; } sub default_type { 'IV'; } sub macro_from_name { my ($self, $item) = @_; my $macro = $item->{name}; $macro = $item->{value} unless defined $macro; $macro; } sub macro_from_item { my ($self, $item) = @_; my $macro = $item->{macro}; $macro = $self->macro_from_name($item) unless defined $macro; $macro; } # Keep to the traditional perl source macro sub memEQ { "memEQ"; } sub params { my ($self, $what) = @_; foreach (sort keys %$what) { warn "ExtUtils::Constant doesn't know how to handle values of type $_" unless defined $XS_Constant{$_}; } my $params = {}; $params->{''} = 1 if $what->{''}; $params->{IV} = 1 if $what->{IV} || $what->{UV} || $what->{PVN}; $params->{NV} = 1 if $what->{NV}; $params->{PV} = 1 if $what->{PV} || $what->{PVN}; $params->{SV} = 1 if $what->{SV}; return $params; } sub C_constant_prefix_param { "aTHX_ "; } sub C_constant_prefix_param_defintion { "pTHX_ "; } sub namelen_param_definition { 'STRLEN ' . $_[0] -> namelen_param; } sub C_constant_other_params_defintion { my ($self, $params) = @_; my $body = ''; $body .= ", int utf8" if $params->{''}; $body .= ", IV *iv_return" if $params->{IV}; $body .= ", NV *nv_return" if $params->{NV}; $body .= ", const char **pv_return" if $params->{PV}; $body .= ", SV **sv_return" if $params->{SV}; $body; } sub C_constant_other_params { my ($self, $params) = @_; my $body = ''; $body .= ", utf8" if $params->{''}; $body .= ", iv_return" if $params->{IV}; $body .= ", nv_return" if $params->{NV}; $body .= ", pv_return" if $params->{PV}; $body .= ", sv_return" if $params->{SV}; $body; } sub dogfood { my ($self, $args, @items) = @_; my ($package, $subname, $default_type, $what, $indent, $breakout) = @{$args}{qw(package subname default_type what indent breakout)}; my $result = <<"EOT"; /* When generated this function returned values for the list of names given in this section of perl code. Rather than manually editing these functions to add or remove constants, which would result in this comment and section of code becoming inaccurate, we recommend that you edit this section of code, and use it to regenerate a new set of constant functions which you then use to replace the originals. Regenerate these constant functions by feeding this entire source file to perl -x #!$^X -w use ExtUtils::Constant qw (constant_types C_constant XS_constant); EOT $result .= $self->dump_names ({default_type=>$default_type, what=>$what, indent=>0, declare_types=>1}, @items); $result .= <<'EOT'; print constant_types(), "\n"; # macro defs EOT $package = perl_stringify($package); $result .= "foreach (C_constant (\"$package\", '$subname', '$default_type', \$types, "; # The form of the indent parameter isn't defined. (Yet) if (defined $indent) { require Data::Dumper; $Data::Dumper::Terse=1; $Data::Dumper::Terse=1; # Not used once. :-) chomp ($indent = Data::Dumper::Dumper ($indent)); $result .= $indent; } else { $result .= 'undef'; } $result .= ", $breakout" . ', @names) ) { print $_, "\n"; # C constant subs } print "\n#### XS Section:\n"; print XS_constant ("' . $package . '", $types); __END__ */ '; $result; } 1; package ExtUtils::CBuilder::Base; use strict; use warnings; use File::Spec; use File::Basename; use Cwd (); use Config; use Text::ParseWords; use IPC::Cmd qw(can_run); use File::Temp qw(tempfile); our $VERSION = '0.280230'; # VERSION # More details about C/C++ compilers: # http://developers.sun.com/sunstudio/documentation/product/compiler.jsp # http://gcc.gnu.org/ # http://publib.boulder.ibm.com/infocenter/comphelp/v101v121/index.jsp # http://msdn.microsoft.com/en-us/vstudio/default.aspx my %cc2cxx = ( # first line order is important to support wrappers like in pkgsrc cc => [ 'c++', 'CC', 'aCC', 'cxx', ], # Sun Studio, HP ANSI C/C++ Compilers gcc => [ 'g++' ], # GNU Compiler Collection xlc => [ 'xlC' ], # IBM C/C++ Set, xlc without thread-safety xlc_r => [ 'xlC_r' ], # IBM C/C++ Set, xlc with thread-safety cl => [ 'cl' ], # Microsoft Visual Studio ); sub new { my $class = shift; my $self = bless {@_}, $class; $self->{properties}{perl} = $class->find_perl_interpreter or warn "Warning: Can't locate your perl binary"; while (my ($k,$v) = each %Config) { $self->{config}{$k} = $v unless exists $self->{config}{$k}; } $self->{config}{cc} = $ENV{CC} if defined $ENV{CC}; $self->{config}{ccflags} = join(" ", $self->{config}{ccflags}, $ENV{CFLAGS}) if defined $ENV{CFLAGS}; $self->{config}{cxx} = $ENV{CXX} if defined $ENV{CXX}; $self->{config}{cxxflags} = $ENV{CXXFLAGS} if defined $ENV{CXXFLAGS}; $self->{config}{ld} = $ENV{LD} if defined $ENV{LD}; $self->{config}{ldflags} = join(" ", $self->{config}{ldflags}, $ENV{LDFLAGS}) if defined $ENV{LDFLAGS}; unless ( exists $self->{config}{cxx} ) { my ($ccbase, $ccpath, $ccsfx ) = fileparse($self->{config}{cc}, qr/\.[^.]*/); ## If the path is just "cc", fileparse returns $ccpath as "./" $ccpath = "" if $self->{config}{cc} =~ /^$ccbase$ccsfx$/; foreach my $cxx (@{$cc2cxx{$ccbase}}) { my $cxx1 = File::Spec->catfile( $ccpath, $cxx . $ccsfx); if( can_run( $cxx1 ) ) { $self->{config}{cxx} = $cxx1; last; } my $cxx2 = $cxx . $ccsfx; if( can_run( $cxx2 ) ) { $self->{config}{cxx} = $cxx2; last; } if( can_run( $cxx ) ) { $self->{config}{cxx} = $cxx; last; } } unless ( exists $self->{config}{cxx} ) { $self->{config}{cxx} = $self->{config}{cc}; my $cflags = $self->{config}{ccflags}; $self->{config}{cxxflags} = '-x c++'; $self->{config}{cxxflags} .= " $cflags" if defined $cflags; } } return $self; } sub find_perl_interpreter { my $perl; File::Spec->file_name_is_absolute($perl = $^X) or -f ($perl = $Config::Config{perlpath}) or ($perl = $^X); # XXX how about using IPC::Cmd::can_run here? return $perl; } sub add_to_cleanup { my $self = shift; foreach (@_) { $self->{files_to_clean}{$_} = 1; } } sub cleanup { my $self = shift; foreach my $file (keys %{$self->{files_to_clean}}) { unlink $file; } } sub get_config { return %{ $_[0]->{config} }; } sub object_file { my ($self, $filename) = @_; # File name, minus the suffix (my $file_base = $filename) =~ s/\.[^.]+$//; return "$file_base$self->{config}{obj_ext}"; } sub arg_include_dirs { my $self = shift; return map {"-I$_"} @_; } sub arg_nolink { '-c' } sub arg_object_file { my ($self, $file) = @_; return ('-o', $file); } sub arg_share_object_file { my ($self, $file) = @_; return ($self->split_like_shell($self->{config}{lddlflags}), '-o', $file); } sub arg_exec_file { my ($self, $file) = @_; return ('-o', $file); } sub arg_defines { my ($self, %args) = @_; return map "-D$_=$args{$_}", sort keys %args; } sub compile { my ($self, %args) = @_; die "Missing 'source' argument to compile()" unless defined $args{source}; my $cf = $self->{config}; # For convenience my $object_file = $args{object_file} ? $args{object_file} : $self->object_file($args{source}); my $include_dirs_ref = (exists($args{include_dirs}) && ref($args{include_dirs}) ne "ARRAY") ? [ $args{include_dirs} ] : $args{include_dirs}; my @include_dirs = $self->arg_include_dirs( @{ $include_dirs_ref || [] }, $self->perl_inc(), ); my @defines = $self->arg_defines( %{$args{defines} || {}} ); my @extra_compiler_flags = $self->split_like_shell($args{extra_compiler_flags}); my @cccdlflags = $self->split_like_shell($cf->{cccdlflags}); my @ccflags = $self->split_like_shell($args{'C++'} ? $cf->{cxxflags} : $cf->{ccflags}); my @optimize = $self->split_like_shell($cf->{optimize}); my @flags = ( @include_dirs, @defines, @cccdlflags, @extra_compiler_flags, $self->arg_nolink, @ccflags, @optimize, $self->arg_object_file($object_file), ); my @cc = $self->split_like_shell($args{'C++'} ? $cf->{cxx} : $cf->{cc}); $self->do_system(@cc, @flags, $args{source}) or die "error building $object_file from '$args{source}'"; return $object_file; } sub have_compiler { my ($self, $is_cplusplus) = @_; my $have_compiler_flag = $is_cplusplus ? "have_cxx" : "have_cc"; my $suffix = $is_cplusplus ? ".cc" : ".c"; return $self->{$have_compiler_flag} if defined $self->{$have_compiler_flag}; my $result; my $attempts = 3; # tmpdir has issues for some people so fall back to current dir # don't clobber existing files (rare, but possible) my ( $FH, $tmpfile ) = tempfile( "compilet-XXXXX", SUFFIX => $suffix ); binmode $FH; if ( $is_cplusplus ) { print $FH "class Bogus { public: int boot_compilet() { return 1; } };\n"; } else { print $FH "int boot_compilet() { return 1; }\n"; } close $FH; my ($obj_file, @lib_files); eval { local $^W = 0; local $self->{quiet} = 1; $obj_file = $self->compile('C++' => $is_cplusplus, source => $tmpfile); @lib_files = $self->link(objects => $obj_file, module_name => 'compilet'); }; $result = $@ ? 0 : 1; foreach (grep defined, $tmpfile, $obj_file, @lib_files) { 1 while unlink; } return $self->{$have_compiler_flag} = $result; } sub have_cplusplus { push @_, 1; goto &have_compiler; } sub lib_file { my ($self, $dl_file, %args) = @_; $dl_file =~ s/\.[^.]+$//; $dl_file =~ tr/"//d; if (defined $args{module_name} and length $args{module_name}) { # Need to create with the same name as DynaLoader will load with. require DynaLoader; if (defined &DynaLoader::mod2fname) { my $lib = DynaLoader::mod2fname([split /::/, $args{module_name}]); my ($dev, $lib_dir, undef) = File::Spec->splitpath($dl_file); $dl_file = File::Spec->catpath($dev, $lib_dir, $lib); } } $dl_file .= ".$self->{config}{dlext}"; return $dl_file; } sub exe_file { my ($self, $dl_file) = @_; $dl_file =~ s/\.[^.]+$//; $dl_file =~ tr/"//d; return "$dl_file$self->{config}{_exe}"; } sub need_prelink { 0 } sub extra_link_args_after_prelink { return } sub prelink { my ($self, %args) = @_; my ($dl_file_out, $mksymlists_args) = _prepare_mksymlists_args(\%args); require ExtUtils::Mksymlists; # dl. abbrev for dynamic library ExtUtils::Mksymlists::Mksymlists( %{ $mksymlists_args } ); # Mksymlists will create one of these files return grep -e, map "$dl_file_out.$_", qw(ext def opt); } sub _prepare_mksymlists_args { my $args = shift; ($args->{dl_file} = $args->{dl_name}) =~ s/.*::// unless $args->{dl_file}; my %mksymlists_args = ( DL_VARS => $args->{dl_vars} || [], DL_FUNCS => $args->{dl_funcs} || {}, FUNCLIST => $args->{dl_func_list} || [], IMPORTS => $args->{dl_imports} || {}, NAME => $args->{dl_name}, # Name of the Perl module DLBASE => $args->{dl_base}, # Basename of DLL file FILE => $args->{dl_file}, # Dir + Basename of symlist file VERSION => (defined $args->{dl_version} ? $args->{dl_version} : '0.0'), ); return ($args->{dl_file}, \%mksymlists_args); } sub link { my ($self, %args) = @_; return $self->_do_link('lib_file', lddl => 1, %args); } sub link_executable { my ($self, %args) = @_; return $self->_do_link('exe_file', lddl => 0, %args); } sub _do_link { my ($self, $type, %args) = @_; my $cf = $self->{config}; # For convenience my $objects = delete $args{objects}; $objects = [$objects] unless ref $objects; my $out = $args{$type} || $self->$type($objects->[0], %args); my @temp_files; @temp_files = $self->prelink(%args, dl_name => $args{module_name}) if $args{lddl} && $self->need_prelink; my @linker_flags = ( $self->split_like_shell($args{extra_linker_flags}), $self->extra_link_args_after_prelink( %args, dl_name => $args{module_name}, prelink_res => \@temp_files ) ); my @output = $args{lddl} ? $self->arg_share_object_file($out) : $self->arg_exec_file($out); my @shrp = $self->split_like_shell($cf->{shrpenv}); my @ld = $self->split_like_shell($cf->{ld}); $self->do_system(@shrp, @ld, @output, @$objects, @linker_flags) or die "error building $out from @$objects"; return wantarray ? ($out, @temp_files) : $out; } sub do_system { my ($self, @cmd) = @_; print "@cmd\n" if !$self->{quiet}; return !system(@cmd); } sub split_like_shell { my ($self, $string) = @_; return () unless defined($string); return @$string if UNIVERSAL::isa($string, 'ARRAY'); $string =~ s/^\s+|\s+$//g; return () unless length($string); # Text::ParseWords replaces all 'escaped' characters with themselves, which completely # breaks paths under windows. As such, we forcibly replace backwards slashes with forward # slashes on windows. $string =~ s@\\@/@g if $^O eq 'MSWin32'; return Text::ParseWords::shellwords($string); } # if building perl, perl's main source directory sub perl_src { # N.B. makemaker actually searches regardless of PERL_CORE, but # only squawks at not finding it if PERL_CORE is set return unless $ENV{PERL_CORE}; my $Updir = File::Spec->updir; my $dir = File::Spec->curdir; # Try up to 5 levels upwards for (0..10) { if ( -f File::Spec->catfile($dir,"config_h.SH") && -f File::Spec->catfile($dir,"perl.h") && -f File::Spec->catfile($dir,"lib","Exporter.pm") ) { return Cwd::realpath( $dir ); } $dir = File::Spec->catdir($dir, $Updir); } warn "PERL_CORE is set but I can't find your perl source!\n"; return ''; # return empty string if $ENV{PERL_CORE} but can't find dir ??? } # directory of perl's include files sub perl_inc { my $self = shift; $self->perl_src() || File::Spec->catdir($self->{config}{archlibexp},"CORE"); } sub DESTROY { my $self = shift; local($., $@, $!, $^E, $?); $self->cleanup(); } 1; # vim: ts=2 sw=2 et: package ExtUtils::CBuilder::Platform::Windows; use strict; use warnings; use File::Basename; use File::Spec; use ExtUtils::CBuilder::Base; use IO::File; our $VERSION = '0.280230'; # VERSION our @ISA = qw(ExtUtils::CBuilder::Base); sub new { my $class = shift; my $self = $class->SUPER::new(@_); my $cf = $self->{config}; # Inherit from an appropriate compiler driver class my $driver = "ExtUtils::CBuilder::Platform::Windows::" . $self->_compiler_type; eval "require $driver" or die "Could not load compiler driver: $@"; unshift @ISA, $driver; return $self; } sub _compiler_type { my $self = shift; my $cc = $self->{config}{cc}; return ( $cc =~ /cl(\.exe)?$/ ? 'MSVC' : $cc =~ /bcc32(\.exe)?$/ ? 'BCC' : 'GCC'); } sub split_like_shell { # Since Windows will pass the whole command string (not an argument # array) to the target program and make the program parse it itself, # we don't actually need to do any processing here. (my $self, local $_) = @_; return @$_ if defined() && UNIVERSAL::isa($_, 'ARRAY'); return unless defined() && length(); return ($_); } sub do_system { # See above my $self = shift; my $cmd = join(" ", grep length, map {$a=$_;$a=~s/\t/ /g;$a=~s/^\s+|\s+$//;$a} grep defined, @_); return $self->SUPER::do_system($cmd); } sub arg_defines { my ($self, %args) = @_; s/"/\\"/g foreach values %args; return map qq{"-D$_=$args{$_}"}, sort keys %args; } sub compile { my ($self, %args) = @_; my $cf = $self->{config}; die "Missing 'source' argument to compile()" unless defined $args{source}; $args{include_dirs} = [ $args{include_dirs} ] if exists($args{include_dirs}) && ref($args{include_dirs}) ne "ARRAY"; my ($basename, $srcdir) = ( File::Basename::fileparse($args{source}, '\.[^.]+$') )[0,1]; $srcdir ||= File::Spec->curdir(); my @defines = $self->arg_defines( %{ $args{defines} || {} } ); my %spec = ( srcdir => $srcdir, builddir => $srcdir, basename => $basename, source => $args{source}, output => $args{object_file} || File::Spec->catfile($srcdir, $basename) . $cf->{obj_ext}, cc => $cf->{cc}, cflags => [ $self->split_like_shell($cf->{ccflags}), $self->split_like_shell($cf->{cccdlflags}), $self->split_like_shell($args{extra_compiler_flags}), ], optimize => [ $self->split_like_shell($cf->{optimize}) ], defines => \@defines, includes => [ @{$args{include_dirs} || []} ], perlinc => [ $self->perl_inc(), $self->split_like_shell($cf->{incpath}), ], use_scripts => 1, # XXX provide user option to change this??? ); $self->normalize_filespecs( \$spec{source}, \$spec{output}, $spec{includes}, $spec{perlinc}, ); my @cmds = $self->format_compiler_cmd(%spec); while ( my $cmd = shift @cmds ) { $self->do_system( @$cmd ) or die "error building $cf->{dlext} file from '$args{source}'"; } (my $out = $spec{output}) =~ tr/'"//d; return $out; } sub need_prelink { 1 } sub link { my ($self, %args) = @_; my $cf = $self->{config}; my @objects = ( ref $args{objects} eq 'ARRAY' ? @{$args{objects}} : $args{objects} ); my $to = join '', (File::Spec->splitpath($objects[0]))[0,1]; $to ||= File::Spec->curdir(); (my $file_base = $args{module_name}) =~ s/.*:://; my $output = $args{lib_file} || File::Spec->catfile($to, "$file_base.$cf->{dlext}"); # if running in perl source tree, look for libs there, not installed my $lddlflags = $cf->{lddlflags}; my $perl_src = $self->perl_src(); $lddlflags =~ s{\Q$cf->{archlibexp}\E[\\/]CORE}{$perl_src/lib/CORE} if $perl_src; my %spec = ( srcdir => $to, builddir => $to, startup => [ ], objects => \@objects, libs => [ ], output => $output, ld => $cf->{ld}, libperl => $cf->{libperl}, perllibs => [ $self->split_like_shell($cf->{perllibs}) ], libpath => [ $self->split_like_shell($cf->{libpth}) ], lddlflags => [ $self->split_like_shell($lddlflags) ], other_ldflags => [ $self->split_like_shell($args{extra_linker_flags} || '') ], use_scripts => 1, # XXX provide user option to change this??? ); unless ( $spec{basename} ) { ($spec{basename} = $args{module_name}) =~ s/.*:://; } $spec{srcdir} = File::Spec->canonpath( $spec{srcdir} ); $spec{builddir} = File::Spec->canonpath( $spec{builddir} ); $spec{output} ||= File::Spec->catfile( $spec{builddir}, $spec{basename} . '.'.$cf->{dlext} ); $spec{manifest} ||= $spec{output} . '.manifest'; $spec{implib} ||= File::Spec->catfile( $spec{builddir}, $spec{basename} . $cf->{lib_ext} ); $spec{explib} ||= File::Spec->catfile( $spec{builddir}, $spec{basename} . '.exp' ); if ($cf->{cc} eq 'cl') { $spec{dbg_file} ||= File::Spec->catfile( $spec{builddir}, $spec{basename} . '.pdb' ); } elsif ($cf->{cc} eq 'bcc32') { $spec{dbg_file} ||= File::Spec->catfile( $spec{builddir}, $spec{basename} . '.tds' ); } $spec{def_file} ||= File::Spec->catfile( $spec{srcdir} , $spec{basename} . '.def' ); $spec{base_file} ||= File::Spec->catfile( $spec{srcdir} , $spec{basename} . '.base' ); $self->add_to_cleanup( grep defined, @{[ @spec{qw(manifest implib explib dbg_file def_file base_file map_file)} ]} ); foreach my $opt ( qw(output manifest implib explib dbg_file def_file map_file base_file) ) { $self->normalize_filespecs( \$spec{$opt} ); } foreach my $opt ( qw(libpath startup objects) ) { $self->normalize_filespecs( $spec{$opt} ); } (my $def_base = $spec{def_file}) =~ tr/'"//d; $def_base =~ s/\.def$//; $self->prelink( %args, dl_name => $args{module_name}, dl_file => $def_base, dl_base => $spec{basename} ); my @cmds = $self->format_linker_cmd(%spec); while ( my $cmd = shift @cmds ) { $self->do_system( @$cmd ); } $spec{output} =~ tr/'"//d; return wantarray ? grep defined, @spec{qw[output manifest implib explib dbg_file def_file map_file base_file]} : $spec{output}; } # canonize & quote paths sub normalize_filespecs { my ($self, @specs) = @_; foreach my $spec ( grep defined, @specs ) { if ( ref $spec eq 'ARRAY') { $self->normalize_filespecs( map {\$_} grep defined, @$spec ) } elsif ( ref $spec eq 'SCALAR' ) { $$spec =~ tr/"//d if $$spec; next unless $$spec; $$spec = '"' . File::Spec->canonpath($$spec) . '"'; } elsif ( ref $spec eq '' ) { $spec = '"' . File::Spec->canonpath($spec) . '"'; } else { die "Don't know how to normalize " . (ref $spec || $spec) . "\n"; } } } # directory of perl's include files sub perl_inc { my $self = shift; my $perl_src = $self->perl_src(); if ($perl_src) { File::Spec->catdir($perl_src, "lib", "CORE"); } else { File::Spec->catdir($self->{config}{archlibexp},"CORE"); } } 1; __END__ package ExtUtils::CBuilder::Platform::VMS; use warnings; use strict; use ExtUtils::CBuilder::Base; our $VERSION = '0.280230'; # VERSION our @ISA = qw(ExtUtils::CBuilder::Base); use File::Spec::Functions qw(catfile catdir); use Config; # We do prelink, but don't want the parent to redo it. sub need_prelink { 0 } sub arg_defines { my ($self, %args) = @_; s/"/""/g foreach values %args; my @config_defines; # VMS can only have one define qualifier; add the one from config, if any. if ($self->{config}{ccflags} =~ s{/ def[^=]+ =+ \(? ([^\/\)]*) } {}ix) { push @config_defines, $1; } return '' unless keys(%args) || @config_defines; return ('/define=(' . join(',', @config_defines, map "\"$_" . ( length($args{$_}) ? "=$args{$_}" : '') . "\"", sort keys %args) . ')'); } sub arg_include_dirs { my ($self, @dirs) = @_; # VMS can only have one include list, add the one from config. if ($self->{config}{ccflags} =~ s{/inc[^=]+(?:=)+(?:\()?([^\/\)]*)} {}i) { unshift @dirs, $1; } return unless @dirs; return ('/include=(' . join(',', @dirs) . ')'); } # We override the compile method because we consume the includes and defines # parts of ccflags in the process of compiling but don't save those parts # anywhere, so $self->{config}{ccflags} needs to be reset for each compile # operation. sub compile { my ($self, %args) = @_; $self->{config}{ccflags} = $Config{ccflags}; $self->{config}{ccflags} = $ENV{CFLAGS} if defined $ENV{CFLAGS}; return $self->SUPER::compile(%args); } sub _do_link { my ($self, $type, %args) = @_; my $objects = delete $args{objects}; $objects = [$objects] unless ref $objects; if ($args{lddl}) { # prelink will call Mksymlists, which creates the extension-specific # linker options file and populates it with the boot symbol. my @temp_files = $self->prelink(%args, dl_name => $args{module_name}); # We now add the rest of what we need to the linker options file. We # should replicate the functionality of C, # but there is as yet no infrastructure for handling object libraries, # so for now we depend on object files being listed individually on the # command line, which should work for simple cases. We do bring in our # own version of C so that any additional # libraries (including PERLSHR) can be added to the options file. my @optlibs = $self->_liblist_ext( $args{'libs'} ); my $optfile = 'sys$disk:[]' . $temp_files[0]; open my $opt_fh, '>>', $optfile or die "_do_link: Unable to open $optfile: $!"; for my $lib (@optlibs) {print $opt_fh "$lib\n" if length $lib } close $opt_fh; $objects->[-1] .= ','; push @$objects, $optfile . '/OPTIONS,'; # This one not needed for DEC C, but leave for completeness. push @$objects, $self->perl_inc() . 'perlshr_attr.opt/OPTIONS'; } return $self->SUPER::_do_link($type, %args, objects => $objects); } sub arg_nolink { return; } sub arg_object_file { my ($self, $file) = @_; return "/obj=$file"; } sub arg_exec_file { my ($self, $file) = @_; return ("/exe=$file"); } sub arg_share_object_file { my ($self, $file) = @_; return ("$self->{config}{lddlflags}=$file"); } # The following is reproduced almost verbatim from ExtUtils::Liblist::Kid::_vms_ext. # We can't just call that because it's tied up with the MakeMaker object hierarchy. sub _liblist_ext { my($self, $potential_libs,$verbose,$give_libs) = @_; $verbose ||= 0; my(@crtls,$crtlstr); @crtls = ( ($self->{'config'}{'ldflags'} =~ m-/Debug-i ? $self->{'config'}{'dbgprefix'} : '') . 'PerlShr/Share' ); push(@crtls, grep { not /\(/ } split /\s+/, $self->{'config'}{'perllibs'}); push(@crtls, grep { not /\(/ } split /\s+/, $self->{'config'}{'libc'}); # In general, we pass through the basic libraries from %Config unchanged. # The one exception is that if we're building in the Perl source tree, and # a library spec could be resolved via a logical name, we go to some trouble # to ensure that the copy in the local tree is used, rather than one to # which a system-wide logical may point. if ($self->perl_src) { my($lib,$locspec,$type); foreach $lib (@crtls) { if (($locspec,$type) = $lib =~ m{^([\w\$-]+)(/\w+)?} and $locspec =~ /perl/i) { if (lc $type eq '/share') { $locspec .= $self->{'config'}{'exe_ext'}; } elsif (lc $type eq '/library') { $locspec .= $self->{'config'}{'lib_ext'}; } else { $locspec .= $self->{'config'}{'obj_ext'}; } $locspec = catfile($self->perl_src, $locspec); $lib = "$locspec$type" if -e $locspec; } } } $crtlstr = @crtls ? join(' ',@crtls) : ''; unless ($potential_libs) { warn "Result:\n\tEXTRALIBS: \n\tLDLOADLIBS: $crtlstr\n" if $verbose; return ('', '', $crtlstr, '', ($give_libs ? [] : ())); } my(@dirs,@libs,$dir,$lib,%found,@fndlibs,$ldlib); my $cwd = cwd(); my($so,$lib_ext,$obj_ext) = @{$self->{'config'}}{'so','lib_ext','obj_ext'}; # List of common Unix library names and their VMS equivalents # (VMS equivalent of '' indicates that the library is automatically # searched by the linker, and should be skipped here.) my(@flibs, %libs_seen); my %libmap = ( 'm' => '', 'f77' => '', 'F77' => '', 'V77' => '', 'c' => '', 'malloc' => '', 'crypt' => '', 'resolv' => '', 'c_s' => '', 'socket' => '', 'X11' => 'DECW$XLIBSHR', 'Xt' => 'DECW$XTSHR', 'Xm' => 'DECW$XMLIBSHR', 'Xmu' => 'DECW$XMULIBSHR'); warn "Potential libraries are '$potential_libs'\n" if $verbose; # First, sort out directories and library names in the input foreach $lib (split ' ',$potential_libs) { push(@dirs,$1), next if $lib =~ /^-L(.*)/; push(@dirs,$lib), next if $lib =~ /[:>\]]$/; push(@dirs,$lib), next if -d $lib; push(@libs,$1), next if $lib =~ /^-l(.*)/; push(@libs,$lib); } push(@dirs,split(' ',$self->{'config'}{'libpth'})); # Now make sure we've got VMS-syntax absolute directory specs # (We don't, however, check whether someone's hidden a relative # path in a logical name.) foreach $dir (@dirs) { unless (-d $dir) { warn "Skipping nonexistent Directory $dir\n" if $verbose > 1; $dir = ''; next; } warn "Resolving directory $dir\n" if $verbose; if (!File::Spec->file_name_is_absolute($dir)) { $dir = catdir($cwd,$dir); } } @dirs = grep { length($_) } @dirs; unshift(@dirs,''); # Check each $lib without additions first LIB: foreach $lib (@libs) { if (exists $libmap{$lib}) { next unless length $libmap{$lib}; $lib = $libmap{$lib}; } my(@variants,$variant,$cand); my($ctype) = ''; # If we don't have a file type, consider it a possibly abbreviated name and # check for common variants. We try these first to grab libraries before # a like-named executable image (e.g. -lperl resolves to perlshr.exe # before perl.exe). if ($lib !~ /\.[^:>\]]*$/) { push(@variants,"${lib}shr","${lib}rtl","${lib}lib"); push(@variants,"lib$lib") if $lib !~ /[:>\]]/; } push(@variants,$lib); warn "Looking for $lib\n" if $verbose; foreach $variant (@variants) { my($fullname, $name); foreach $dir (@dirs) { my($type); $name = "$dir$variant"; warn "\tChecking $name\n" if $verbose > 2; $fullname = VMS::Filespec::rmsexpand($name); if (defined $fullname and -f $fullname) { # It's got its own suffix, so we'll have to figure out the type if ($fullname =~ /(?:$so|exe)$/i) { $type = 'SHR'; } elsif ($fullname =~ /(?:$lib_ext|olb)$/i) { $type = 'OLB'; } elsif ($fullname =~ /(?:$obj_ext|obj)$/i) { warn "Note (probably harmless): " ."Plain object file $fullname found in library list\n"; $type = 'OBJ'; } else { warn "Note (probably harmless): " ."Unknown library type for $fullname; assuming shared\n"; $type = 'SHR'; } } elsif (-f ($fullname = VMS::Filespec::rmsexpand($name,$so)) or -f ($fullname = VMS::Filespec::rmsexpand($name,'.exe'))) { $type = 'SHR'; $name = $fullname unless $fullname =~ /exe;?\d*$/i; } elsif (not length($ctype) and # If we've got a lib already, # don't bother ( -f ($fullname = VMS::Filespec::rmsexpand($name,$lib_ext)) or -f ($fullname = VMS::Filespec::rmsexpand($name,'.olb')))) { $type = 'OLB'; $name = $fullname unless $fullname =~ /olb;?\d*$/i; } elsif (not length($ctype) and # If we've got a lib already, # don't bother ( -f ($fullname = VMS::Filespec::rmsexpand($name,$obj_ext)) or -f ($fullname = VMS::Filespec::rmsexpand($name,'.obj')))) { warn "Note (probably harmless): " ."Plain object file $fullname found in library list\n"; $type = 'OBJ'; $name = $fullname unless $fullname =~ /obj;?\d*$/i; } if (defined $type) { $ctype = $type; $cand = $name; last if $ctype eq 'SHR'; } } if ($ctype) { push @{$found{$ctype}}, $cand; warn "\tFound as $cand (really $fullname), type $ctype\n" if $verbose > 1; push @flibs, $name unless $libs_seen{$fullname}++; next LIB; } } warn "Note (probably harmless): " ."No library found for $lib\n"; } push @fndlibs, @{$found{OBJ}} if exists $found{OBJ}; push @fndlibs, map { "$_/Library" } @{$found{OLB}} if exists $found{OLB}; push @fndlibs, map { "$_/Share" } @{$found{SHR}} if exists $found{SHR}; $lib = join(' ',@fndlibs); $ldlib = $crtlstr ? "$lib $crtlstr" : $lib; warn "Result:\n\tEXTRALIBS: $lib\n\tLDLOADLIBS: $ldlib\n" if $verbose; wantarray ? ($lib, '', $ldlib, '', ($give_libs ? \@flibs : ())) : $lib; } 1; package ExtUtils::CBuilder::Platform::os2; use warnings; use strict; use ExtUtils::CBuilder::Platform::Unix; our $VERSION = '0.280230'; # VERSION our @ISA = qw(ExtUtils::CBuilder::Platform::Unix); sub need_prelink { 1 } sub prelink { # Generate import libraries (XXXX currently near .DEF; should be near DLL!) my $self = shift; my %args = @_; my @res = $self->SUPER::prelink(%args); die "Unexpected number of DEF files" unless @res == 1; die "Can't find DEF file in the output" unless $res[0] =~ m,^(.*)\.def$,si; my $libname = "$1$self->{config}{lib_ext}"; # Put .LIB file near .DEF file $self->do_system('emximp', '-o', $libname, $res[0]) or die "emxexp: res=$?"; return (@res, $libname); } sub _do_link { my $self = shift; my ($how, %args) = @_; if ($how eq 'lib_file' and (defined $args{module_name} and length $args{module_name})) { # Now know the basename, find directory parts via lib_file, or objects my $objs = ( (ref $args{objects}) ? $args{objects} : [$args{objects}] ); my $near_obj = $self->lib_file(@$objs); my $exp_dir = ($near_obj =~ m,(.*)[/\\],s ? "$1/" : '' ); $args{dl_file} = $1 if $near_obj =~ m,(.*)\.,s; # put ExportList near OBJ # XXX _do_link does not have place to put libraries? push @$objs, $self->perl_inc() . "/libperl$self->{config}{lib_ext}"; $args{objects} = $objs; } # Some 'env' do exec(), thus return too early when run from ksh; # To avoid 'env', remove (useless) shrpenv local $self->{config}{shrpenv} = ''; return $self->SUPER::_do_link($how, %args); } sub extra_link_args_after_prelink { # Add .DEF file to the link line my ($self, %args) = @_; my @DEF = grep /\.def$/i, @{$args{prelink_res}}; die "More than one .def files created by 'prelink' stage" if @DEF > 1; # XXXX No "$how" argument here, so how to test for dynamic link? die "No .def file created by 'prelink' stage" unless @DEF or not @{$args{prelink_res}}; my @after_libs = ($OS2::is_aout ? () : $self->perl_inc() . "/libperl_override$self->{config}{lib_ext}"); # , "-L", "-lperl" (@after_libs, @DEF); } sub link_executable { # ldflags is not expecting .exe extension given on command line; remove -Zexe my $self = shift; local $self->{config}{ldflags} = $self->{config}{ldflags}; $self->{config}{ldflags} =~ s/(?SUPER::link_executable(@_); } 1; package ExtUtils::CBuilder::Platform::dec_osf; use warnings; use strict; use ExtUtils::CBuilder::Platform::Unix; use File::Spec; our $VERSION = '0.280230'; # VERSION our @ISA = qw(ExtUtils::CBuilder::Platform::Unix); sub link_executable { my $self = shift; # $Config{ld} is 'ld' but that won't work: use the cc instead. local $self->{config}{ld} = $self->{config}{cc}; return $self->SUPER::link_executable(@_); } 1; package ExtUtils::CBuilder::Platform::darwin; use warnings; use strict; use ExtUtils::CBuilder::Platform::Unix; our $VERSION = '0.280230'; # VERSION our @ISA = qw(ExtUtils::CBuilder::Platform::Unix); sub compile { my $self = shift; my $cf = $self->{config}; # -flat_namespace isn't a compile flag, it's a linker flag. But # it's mistakenly in Config.pm as both. Make the correction here. local $cf->{ccflags} = $cf->{ccflags}; $cf->{ccflags} =~ s/-flat_namespace//; $self->SUPER::compile(@_); } 1; package ExtUtils::CBuilder::Platform::cygwin; use warnings; use strict; use File::Spec; use ExtUtils::CBuilder::Platform::Unix; our $VERSION = '0.280230'; # VERSION our @ISA = qw(ExtUtils::CBuilder::Platform::Unix); # TODO: If a specific exe_file name is requested, if the exe created # doesn't have that name, we might want to rename it. Apparently asking # for an exe of "foo" might result in "foo.exe". Alternatively, we should # make sure the return value is correctly "foo.exe". # C.f http://rt.cpan.org/Public/Bug/Display.html?id=41003 sub link_executable { my $self = shift; return $self->SUPER::link_executable(@_); } sub link { my ($self, %args) = @_; my $lib = $self->{config}{useshrplib} ? 'libperl.dll.a' : 'libperl.a'; $args{extra_linker_flags} = [ File::Spec->catfile($self->perl_inc(), $lib), $self->split_like_shell($args{extra_linker_flags}) ]; return $self->SUPER::link(%args); } 1; package ExtUtils::CBuilder::Platform::android; use warnings; use strict; use File::Spec; use ExtUtils::CBuilder::Platform::Unix; use Config; our $VERSION = '0.280230'; # VERSION our @ISA = qw(ExtUtils::CBuilder::Platform::Unix); # The Android linker will not recognize symbols from # libperl unless the module explicitly depends on it. sub link { my ($self, %args) = @_; if ($self->{config}{useshrplib} eq 'true') { $args{extra_linker_flags} = [ $self->split_like_shell($args{extra_linker_flags}), '-L' . $self->perl_inc(), '-lperl', $self->split_like_shell($Config{perllibs}), ]; } # Several modules on CPAN rather rightfully expect being # able to pass $so_file to DynaLoader::dl_load_file and # have it Just Work. However, $so_file will more likely # than not be a relative path, and unless the module # author subclasses MakeMaker/Module::Build to modify # LD_LIBRARY_PATH, which would be insane, Android's linker # won't find the .so # So we make this all work by returning an absolute path. my($so_file, @so_tmps) = $self->SUPER::link(%args); $so_file = File::Spec->rel2abs($so_file); return wantarray ? ($so_file, @so_tmps) : $so_file; } 1; package ExtUtils::CBuilder::Platform::aix; use warnings; use strict; use ExtUtils::CBuilder::Platform::Unix; use File::Spec; our $VERSION = '0.280230'; # VERSION our @ISA = qw(ExtUtils::CBuilder::Platform::Unix); sub need_prelink { 1 } sub link { my ($self, %args) = @_; my $cf = $self->{config}; (my $baseext = $args{module_name}) =~ s/.*:://; my $perl_inc = $self->perl_inc(); # Massage some very naughty bits in %Config local $cf->{lddlflags} = $cf->{lddlflags}; for ($cf->{lddlflags}) { s/\Q$(BASEEXT)\E/$baseext/; s/\Q$(PERL_INC)\E/$perl_inc/; } return $self->SUPER::link(%args); } 1; package ExtUtils::CBuilder::Platform::Unix; use warnings; use strict; use ExtUtils::CBuilder::Base; our $VERSION = '0.280230'; # VERSION our @ISA = qw(ExtUtils::CBuilder::Base); sub link_executable { my $self = shift; # On some platforms (which ones??) $Config{cc} seems to be a better # bet for linking executables than $Config{ld}. Cygwin is a notable # exception. local $self->{config}{ld} = $self->{config}{cc} . " " . $self->{config}{ldflags}; return $self->SUPER::link_executable(@_); } sub link { my $self = shift; my $cf = $self->{config}; # Some platforms (notably Mac OS X 10.3, but some others too) expect # the syntax "FOO=BAR /bin/command arg arg" to work in %Config # (notably $Config{ld}). It usually works in system(SCALAR), but we # use system(LIST). We fix it up here with 'env'. local $cf->{ld} = $cf->{ld}; if (ref $cf->{ld}) { unshift @{$cf->{ld}}, 'env' if $cf->{ld}[0] =~ /^\s*\w+=/; } else { $cf->{ld} =~ s/^(\s*\w+=)/env $1/; } return $self->SUPER::link(@_); } 1; package ExtUtils::CBuilder::Platform::Windows::MSVC; our $VERSION = '0.280230'; # VERSION use warnings; use strict; sub arg_exec_file { my ($self, $file) = @_; return "/OUT:$file"; } sub format_compiler_cmd { my ($self, %spec) = @_; foreach my $path ( @{ $spec{includes} || [] }, @{ $spec{perlinc} || [] } ) { $path = '-I' . $path; } %spec = $self->write_compiler_script(%spec) if $spec{use_scripts}; return [ grep {defined && length} ( $spec{cc},'-nologo','-c', @{$spec{includes}} , @{$spec{cflags}} , @{$spec{optimize}} , @{$spec{defines}} , @{$spec{perlinc}} , "-Fo$spec{output}" , $spec{source} , ) ]; } sub write_compiler_script { my ($self, %spec) = @_; my $script = File::Spec->catfile( $spec{srcdir}, $spec{basename} . '.ccs' ); $self->add_to_cleanup($script); print "Generating script '$script'\n" if !$self->{quiet}; my $SCRIPT = IO::File->new( ">$script" ) or die( "Could not create script '$script': $!" ); print $SCRIPT join( "\n", map { ref $_ ? @{$_} : $_ } grep defined, delete( @spec{ qw(includes cflags optimize defines perlinc) } ) ); push @{$spec{includes}}, '@"' . $script . '"'; return %spec; } sub format_linker_cmd { my ($self, %spec) = @_; my $cf = $self->{config}; foreach my $path ( @{$spec{libpath}} ) { $path = "-libpath:$path"; } my $output = $spec{output}; my $manifest = $spec{manifest}; $spec{def_file} &&= '-def:' . $spec{def_file}; $spec{output} &&= '-out:' . $spec{output}; $spec{manifest} &&= '-manifest ' . $spec{manifest}; $spec{implib} &&= '-implib:' . $spec{implib}; $spec{map_file} &&= '-map:' . $spec{map_file}; %spec = $self->write_linker_script(%spec) if $spec{use_scripts}; my @cmds; # Stores the series of commands needed to build the module. push @cmds, [ grep {defined && length} ( $spec{ld} , @{$spec{lddlflags}} , @{$spec{libpath}} , @{$spec{other_ldflags}} , @{$spec{startup}} , @{$spec{objects}} , $spec{map_file} , $spec{libperl} , @{$spec{perllibs}} , $spec{def_file} , $spec{implib} , $spec{output} , ) ]; # Embed the manifest file if it exists push @cmds, [ 'if', 'exist', $manifest, 'mt', '-nologo', $spec{manifest}, '-outputresource:' . "$output;2" ]; return @cmds; } sub write_linker_script { my ($self, %spec) = @_; my $script = File::Spec->catfile( $spec{srcdir}, $spec{basename} . '.lds' ); $self->add_to_cleanup($script); print "Generating script '$script'\n" if !$self->{quiet}; my $SCRIPT = IO::File->new( ">$script" ) or die( "Could not create script '$script': $!" ); print $SCRIPT join( "\n", map { ref $_ ? @{$_} : $_ } grep defined, delete( @spec{ qw(lddlflags libpath other_ldflags startup objects libperl perllibs def_file implib map_file) } ) ); push @{$spec{lddlflags}}, '@"' . $script . '"'; return %spec; } 1; package ExtUtils::CBuilder::Platform::Windows::BCC; our $VERSION = '0.280230'; # VERSION use strict; use warnings; sub format_compiler_cmd { my ($self, %spec) = @_; foreach my $path ( @{ $spec{includes} || [] }, @{ $spec{perlinc} || [] } ) { $path = '-I' . $path; } %spec = $self->write_compiler_script(%spec) if $spec{use_scripts}; return [ grep {defined && length} ( $spec{cc}, '-c' , @{$spec{includes}} , @{$spec{cflags}} , @{$spec{optimize}} , @{$spec{defines}} , @{$spec{perlinc}} , "-o$spec{output}" , $spec{source} , ) ]; } sub write_compiler_script { my ($self, %spec) = @_; my $script = File::Spec->catfile( $spec{srcdir}, $spec{basename} . '.ccs' ); $self->add_to_cleanup($script); print "Generating script '$script'\n" if !$self->{quiet}; my $SCRIPT = IO::File->new( ">$script" ) or die( "Could not create script '$script': $!" ); # XXX Borland "response files" seem to be unable to accept macro # definitions containing quoted strings. Escaping strings with # backslash doesn't work, and any level of quotes are stripped. The # result is a floating point number in the source file where a # string is expected. So we leave the macros on the command line. print $SCRIPT join( "\n", map { ref $_ ? @{$_} : $_ } grep defined, delete( @spec{ qw(includes cflags optimize perlinc) } ) ); push @{$spec{includes}}, '@"' . $script . '"'; return %spec; } sub format_linker_cmd { my ($self, %spec) = @_; foreach my $path ( @{$spec{libpath}} ) { $path = "-L$path"; } push( @{$spec{startup}}, 'c0d32.obj' ) unless ( $spec{startup} && @{$spec{startup}} ); %spec = $self->write_linker_script(%spec) if $spec{use_scripts}; return [ grep {defined && length} ( $spec{ld} , @{$spec{lddlflags}} , @{$spec{libpath}} , @{$spec{other_ldflags}} , @{$spec{startup}} , @{$spec{objects}} , ',', $spec{output} , ',', $spec{map_file} , ',', $spec{libperl} , @{$spec{perllibs}} , ',', $spec{def_file} ) ]; } sub write_linker_script { my ($self, %spec) = @_; # To work around Borlands "unique" commandline syntax, # two scripts are used: my $ld_script = File::Spec->catfile( $spec{srcdir}, $spec{basename} . '.lds' ); my $ld_libs = File::Spec->catfile( $spec{srcdir}, $spec{basename} . '.lbs' ); $self->add_to_cleanup($ld_script, $ld_libs); print "Generating scripts '$ld_script' and '$ld_libs'.\n" if !$self->{quiet}; # Script 1: contains options & names of object files. my $LD_SCRIPT = IO::File->new( ">$ld_script" ) or die( "Could not create linker script '$ld_script': $!" ); print $LD_SCRIPT join( " +\n", map { @{$_} } grep defined, delete( @spec{ qw(lddlflags libpath other_ldflags startup objects) } ) ); # Script 2: contains name of libs to link against. my $LD_LIBS = IO::File->new( ">$ld_libs" ) or die( "Could not create linker script '$ld_libs': $!" ); print $LD_LIBS join( " +\n", (delete $spec{libperl} || ''), @{delete $spec{perllibs} || []}, ); push @{$spec{lddlflags}}, '@"' . $ld_script . '"'; push @{$spec{perllibs}}, '@"' . $ld_libs . '"'; return %spec; } 1; package ExtUtils::CBuilder::Platform::Windows::GCC; our $VERSION = '0.280230'; # VERSION use warnings; use strict; sub format_compiler_cmd { my ($self, %spec) = @_; foreach my $path ( @{ $spec{includes} || [] }, @{ $spec{perlinc} || [] } ) { $path = '-I' . $path; } # split off any -arguments included in cc my @cc = split / (?=-)/, $spec{cc}; return [ grep {defined && length} ( @cc, '-c' , @{$spec{includes}} , @{$spec{cflags}} , @{$spec{optimize}} , @{$spec{defines}} , @{$spec{perlinc}} , '-o', $spec{output} , $spec{source} , ) ]; } sub format_linker_cmd { my ($self, %spec) = @_; my $cf = $self->{config}; # The Config.pm variable 'libperl' is hardcoded to the full name # of the perl import library (i.e. 'libperl56.a'). GCC will not # find it unless the 'lib' prefix & the extension are stripped. $spec{libperl} =~ s/^(?:lib)?([^.]+).*$/-l$1/; unshift( @{$spec{other_ldflags}}, '-nostartfiles' ) if ( $spec{startup} && @{$spec{startup}} ); # From ExtUtils::MM_Win32: # ## one thing for GCC/Mingw32: ## we try to overcome non-relocateable-DLL problems by generating ## a (hopefully unique) image-base from the dll's name ## -- BKS, 10-19-1999 File::Basename::basename( $spec{output} ) =~ /(....)(.{0,4})/; $spec{image_base} = sprintf( "0x%x0000", unpack('n', $1 ^ $2) ); %spec = $self->write_linker_script(%spec) if $spec{use_scripts}; foreach my $path ( @{$spec{libpath}} ) { $path = "-L$path"; } my @cmds; # Stores the series of commands needed to build the module. my $DLLTOOL = $cf->{dlltool} || 'dlltool'; push @cmds, [ $DLLTOOL, '--def' , $spec{def_file}, '--output-exp' , $spec{explib} ]; # split off any -arguments included in ld my @ld = split / (?=-)/, $spec{ld}; push @cmds, [ grep {defined && length} ( @ld , '-o', $spec{output} , "-Wl,--base-file,$spec{base_file}" , "-Wl,--image-base,$spec{image_base}" , @{$spec{lddlflags}} , @{$spec{libpath}} , @{$spec{startup}} , @{$spec{objects}} , @{$spec{other_ldflags}} , $spec{libperl} , @{$spec{perllibs}} , $spec{explib} , $spec{map_file} ? ('-Map', $spec{map_file}) : '' ) ]; push @cmds, [ $DLLTOOL, '--def' , $spec{def_file}, '--output-exp' , $spec{explib}, '--base-file' , $spec{base_file} ]; push @cmds, [ grep {defined && length} ( @ld , '-o', $spec{output} , "-Wl,--image-base,$spec{image_base}" , @{$spec{lddlflags}} , @{$spec{libpath}} , @{$spec{startup}} , @{$spec{objects}} , @{$spec{other_ldflags}} , $spec{libperl} , @{$spec{perllibs}} , $spec{explib} , $spec{map_file} ? ('-Map', $spec{map_file}) : '' ) ]; return @cmds; } sub write_linker_script { my ($self, %spec) = @_; my $script = File::Spec->catfile( $spec{srcdir}, $spec{basename} . '.lds' ); $self->add_to_cleanup($script); print "Generating script '$script'\n" if !$self->{quiet}; my $SCRIPT = IO::File->new( ">$script" ) or die( "Could not create script '$script': $!" ); print $SCRIPT ( 'SEARCH_DIR(' . $_ . ")\n" ) for @{delete $spec{libpath} || []}; # gcc takes only one startup file, so the first object in startup is # specified as the startup file and any others are shifted into the # beginning of the list of objects. if ( $spec{startup} && @{$spec{startup}} ) { print $SCRIPT 'STARTUP(' . shift( @{$spec{startup}} ) . ")\n"; unshift @{$spec{objects}}, @{delete $spec{startup} || []}; } print $SCRIPT 'INPUT(' . join( ',', @{delete $spec{objects} || []} ) . ")\n"; print $SCRIPT 'INPUT(' . join( ' ', (delete $spec{libperl} || ''), @{delete $spec{perllibs} || []}, ) . ")\n"; #it is important to keep the order 1.linker_script - 2.other_ldflags unshift @{$spec{other_ldflags}}, '"' . $script . '"'; return %spec; } 1; package ExtUtils::Command::MM; require 5.006; use strict; use warnings; require Exporter; our @ISA = qw(Exporter); our @EXPORT = qw(test_harness pod2man perllocal_install uninstall warn_if_old_packlist test_s cp_nonempty); our $VERSION = '7.34'; $VERSION = eval $VERSION; my $Is_VMS = $^O eq 'VMS'; sub mtime { no warnings 'redefine'; local $@; *mtime = (eval { require Time::HiRes } && defined &Time::HiRes::stat) ? sub { (Time::HiRes::stat($_[0]))[9] } : sub { ( stat($_[0]))[9] } ; goto &mtime; } sub test_harness { require Test::Harness; require File::Spec; $Test::Harness::verbose = shift; # Because Windows doesn't do this for us and listing all the *.t files # out on the command line can blow over its exec limit. require ExtUtils::Command; my @argv = ExtUtils::Command::expand_wildcards(@ARGV); local @INC = @INC; unshift @INC, map { File::Spec->rel2abs($_) } @_; Test::Harness::runtests(sort { lc $a cmp lc $b } @argv); } sub pod2man { local @ARGV = @_ ? @_ : @ARGV; { local $@; if( !eval { require Pod::Man } ) { warn "Pod::Man is not available: $@". "Man pages will not be generated during this install.\n"; return 0; } } require Getopt::Long; # We will cheat and just use Getopt::Long. We fool it by putting # our arguments into @ARGV. Should be safe. my %options = (); Getopt::Long::config ('bundling_override'); Getopt::Long::GetOptions (\%options, 'section|s=s', 'release|r=s', 'center|c=s', 'date|d=s', 'fixed=s', 'fixedbold=s', 'fixeditalic=s', 'fixedbolditalic=s', 'official|o', 'quotes|q=s', 'lax|l', 'name|n=s', 'perm_rw=i', 'utf8|u' ); delete $options{utf8} unless $Pod::Man::VERSION >= 2.17; # If there's no files, don't bother going further. return 0 unless @ARGV; # Official sets --center, but don't override things explicitly set. if ($options{official} && !defined $options{center}) { $options{center} = q[Perl Programmer's Reference Guide]; } # This isn't a valid Pod::Man option and is only accepted for backwards # compatibility. delete $options{lax}; my $count = scalar @ARGV / 2; my $plural = $count == 1 ? 'document' : 'documents'; print "Manifying $count pod $plural\n"; do {{ # so 'next' works my ($pod, $man) = splice(@ARGV, 0, 2); next if ((-e $man) && (mtime($man) > mtime($pod)) && (mtime($man) > mtime("Makefile"))); my $parser = Pod::Man->new(%options); $parser->parse_from_file($pod, $man) or do { warn("Could not install $man\n"); next }; if (exists $options{perm_rw}) { chmod(oct($options{perm_rw}), $man) or do { warn("chmod $options{perm_rw} $man: $!\n"); next }; } }} while @ARGV; return 1; } sub warn_if_old_packlist { my $packlist = $ARGV[0]; return unless -f $packlist; print <<"PACKLIST_WARNING"; WARNING: I have found an old package in $packlist. Please make sure the two installations are not conflicting PACKLIST_WARNING } sub perllocal_install { my($type, $name) = splice(@ARGV, 0, 2); # VMS feeds args as a piped file on STDIN since it usually can't # fit all the args on a single command line. my @mod_info = $Is_VMS ? split /\|/, : @ARGV; my $pod; my $time = gmtime($ENV{SOURCE_DATE_EPOCH} || time); $pod = sprintf <<'POD', scalar($time), $type, $name, $name; =head2 %s: C<%s> L<%s|%s> =over 4 POD do { my($key, $val) = splice(@mod_info, 0, 2); $pod .= < POD } while(@mod_info); $pod .= "=back\n\n"; $pod =~ s/^ //mg; print $pod; return 1; } sub uninstall { my($packlist) = shift @ARGV; require ExtUtils::Install; print <<'WARNING'; Uninstall is unsafe and deprecated, the uninstallation was not performed. We will show what would have been done. WARNING ExtUtils::Install::uninstall($packlist, 1, 1); print <<'WARNING'; Uninstall is unsafe and deprecated, the uninstallation was not performed. Please check the list above carefully, there may be errors. Remove the appropriate files manually. Sorry for the inconvenience. WARNING } sub test_s { exit(-s $ARGV[0] ? 0 : 1); } sub cp_nonempty { my @args = @ARGV; return 0 unless -s $args[0]; require ExtUtils::Command; { local @ARGV = @args[0,1]; ExtUtils::Command::cp(@ARGV); } { local @ARGV = @args[2,1]; ExtUtils::Command::chmod(@ARGV); } } 1; package ExtUtils::ParseXS::Constants; use strict; use warnings; use Symbol; our $VERSION = '3.39'; # FIXME: THESE ARE NOT CONSTANTS! our @InitFileCode; # Note that to reduce maintenance, $PrototypeRegexp is used # by ExtUtils::Typemaps, too! our $PrototypeRegexp = "[" . quotemeta('\$%&*@;[]_') . "]"; our @XSKeywords = qw( REQUIRE BOOT CASE PREINIT INPUT INIT CODE PPCODE OUTPUT CLEANUP ALIAS ATTRS PROTOTYPES PROTOTYPE VERSIONCHECK INCLUDE INCLUDE_COMMAND SCOPE INTERFACE INTERFACE_MACRO C_ARGS POSTCALL OVERLOAD FALLBACK EXPORT_XSUB_SYMBOLS ); our $XSKeywordsAlternation = join('|', @XSKeywords); 1; package ExtUtils::ParseXS::Eval; use strict; use warnings; our $VERSION = '3.39'; sub eval_output_typemap_code { my ($_pxs, $_code, $_other) = @_; my ($Package, $ALIAS, $func_name, $Full_func_name, $pname) = @{$_pxs}{qw(Package ALIAS func_name Full_func_name pname)}; my ($var, $type, $ntype, $subtype, $arg) = @{$_other}{qw(var type ntype subtype arg)}; my $rv = eval $_code; warn $@ if $@; return $rv; } sub eval_input_typemap_code { my ($_pxs, $_code, $_other) = @_; my ($Package, $ALIAS, $func_name, $Full_func_name, $pname) = @{$_pxs}{qw(Package ALIAS func_name Full_func_name pname)}; my ($var, $type, $num, $init, $printed_name, $arg, $ntype, $argoff, $subtype) = @{$_other}{qw(var type num init printed_name arg ntype argoff subtype)}; my $rv = eval $_code; warn $@ if $@; return $rv; } 1; # vim: ts=2 sw=2 et: package ExtUtils::ParseXS::CountLines; use strict; our $VERSION = '3.39'; our $SECTION_END_MARKER; sub TIEHANDLE { my ($class, $cfile, $fh) = @_; $cfile =~ s/\\/\\\\/g; $cfile =~ s/"/\\"/g; $SECTION_END_MARKER = qq{#line --- "$cfile"}; return bless { buffer => '', fh => $fh, line_no => 1, }, $class; } sub PRINT { my $self = shift; for (@_) { $self->{buffer} .= $_; while ($self->{buffer} =~ s/^([^\n]*\n)//) { my $line = $1; ++$self->{line_no}; $line =~ s|^\#line\s+---(?=\s)|#line $self->{line_no}|; print {$self->{fh}} $line; } } } sub PRINTF { my $self = shift; my $fmt = shift; $self->PRINT(sprintf($fmt, @_)); } sub DESTROY { # Not necessary if we're careful to end with a "\n" my $self = shift; print {$self->{fh}} $self->{buffer}; } sub UNTIE { # This sub does nothing, but is necessary for references to be released. } sub end_marker { return $SECTION_END_MARKER; } 1; package ExtUtils::ParseXS::Utilities; use strict; use warnings; use Exporter; use File::Spec; use ExtUtils::ParseXS::Constants (); our $VERSION = '3.39'; our (@ISA, @EXPORT_OK); @ISA = qw(Exporter); @EXPORT_OK = qw( standard_typemap_locations trim_whitespace C_string valid_proto_string process_typemaps map_type standard_XS_defs assign_func_args analyze_preprocessor_statements set_cond Warn current_line_number blurt death check_conditional_preprocessor_statements escape_file_for_line_directive report_typemap_failure ); SCOPE: { my @tm_template; sub standard_typemap_locations { my $include_ref = shift; if (not @tm_template) { @tm_template = qw(typemap); my $updir = File::Spec->updir(); foreach my $dir ( File::Spec->catdir(($updir) x 1), File::Spec->catdir(($updir) x 2), File::Spec->catdir(($updir) x 3), File::Spec->catdir(($updir) x 4), ) { unshift @tm_template, File::Spec->catfile($dir, 'typemap'); unshift @tm_template, File::Spec->catfile($dir, lib => ExtUtils => 'typemap'); } } my @tm = @tm_template; foreach my $dir (@{ $include_ref}) { my $file = File::Spec->catfile($dir, ExtUtils => 'typemap'); unshift @tm, $file if -e $file; } return @tm; } } # end SCOPE sub trim_whitespace { $_[0] =~ s/^\s+|\s+$//go; } sub C_string { my($string) = @_; $string =~ s[\\][\\\\]g; $string; } sub valid_proto_string { my ($string) = @_; if ( $string =~ /^$ExtUtils::ParseXS::Constants::PrototypeRegexp+$/ ) { return $string; } return 0; } sub process_typemaps { my ($tmap, $pwd) = @_; my @tm = ref $tmap ? @{$tmap} : ($tmap); foreach my $typemap (@tm) { die "Can't find $typemap in $pwd\n" unless -r $typemap; } push @tm, standard_typemap_locations( \@INC ); require ExtUtils::Typemaps; my $typemap = ExtUtils::Typemaps->new; foreach my $typemap_loc (@tm) { next unless -f $typemap_loc; # skip directories, binary files etc. warn("Warning: ignoring non-text typemap file '$typemap_loc'\n"), next unless -T $typemap_loc; $typemap->merge(file => $typemap_loc, replace => 1); } return $typemap; } sub map_type { my ($self, $type, $varname) = @_; # C++ has :: in types too so skip this $type =~ tr/:/_/ unless $self->{RetainCplusplusHierarchicalTypes}; $type =~ s/^array\(([^,]*),(.*)\).*/$1 */s; if ($varname) { if ($type =~ / \( \s* \* (?= \s* \) ) /xg) { (substr $type, pos $type, 0) = " $varname "; } else { $type .= "\t$varname"; } } return $type; } sub standard_XS_defs { print <<"EOF"; #ifndef PERL_UNUSED_VAR # define PERL_UNUSED_VAR(var) if (0) var = var #endif #ifndef dVAR # define dVAR dNOOP #endif /* This stuff is not part of the API! You have been warned. */ #ifndef PERL_VERSION_DECIMAL # define PERL_VERSION_DECIMAL(r,v,s) (r*1000000 + v*1000 + s) #endif #ifndef PERL_DECIMAL_VERSION # define PERL_DECIMAL_VERSION \\ PERL_VERSION_DECIMAL(PERL_REVISION,PERL_VERSION,PERL_SUBVERSION) #endif #ifndef PERL_VERSION_GE # define PERL_VERSION_GE(r,v,s) \\ (PERL_DECIMAL_VERSION >= PERL_VERSION_DECIMAL(r,v,s)) #endif #ifndef PERL_VERSION_LE # define PERL_VERSION_LE(r,v,s) \\ (PERL_DECIMAL_VERSION <= PERL_VERSION_DECIMAL(r,v,s)) #endif /* XS_INTERNAL is the explicit static-linkage variant of the default * XS macro. * * XS_EXTERNAL is the same as XS_INTERNAL except it does not include * "STATIC", ie. it exports XSUB symbols. You probably don't want that * for anything but the BOOT XSUB. * * See XSUB.h in core! */ /* TODO: This might be compatible further back than 5.10.0. */ #if PERL_VERSION_GE(5, 10, 0) && PERL_VERSION_LE(5, 15, 1) # undef XS_EXTERNAL # undef XS_INTERNAL # if defined(__CYGWIN__) && defined(USE_DYNAMIC_LOADING) # define XS_EXTERNAL(name) __declspec(dllexport) XSPROTO(name) # define XS_INTERNAL(name) STATIC XSPROTO(name) # endif # if defined(__SYMBIAN32__) # define XS_EXTERNAL(name) EXPORT_C XSPROTO(name) # define XS_INTERNAL(name) EXPORT_C STATIC XSPROTO(name) # endif # ifndef XS_EXTERNAL # if defined(HASATTRIBUTE_UNUSED) && !defined(__cplusplus) # define XS_EXTERNAL(name) void name(pTHX_ CV* cv __attribute__unused__) # define XS_INTERNAL(name) STATIC void name(pTHX_ CV* cv __attribute__unused__) # else # ifdef __cplusplus # define XS_EXTERNAL(name) extern "C" XSPROTO(name) # define XS_INTERNAL(name) static XSPROTO(name) # else # define XS_EXTERNAL(name) XSPROTO(name) # define XS_INTERNAL(name) STATIC XSPROTO(name) # endif # endif # endif #endif /* perl >= 5.10.0 && perl <= 5.15.1 */ /* The XS_EXTERNAL macro is used for functions that must not be static * like the boot XSUB of a module. If perl didn't have an XS_EXTERNAL * macro defined, the best we can do is assume XS is the same. * Dito for XS_INTERNAL. */ #ifndef XS_EXTERNAL # define XS_EXTERNAL(name) XS(name) #endif #ifndef XS_INTERNAL # define XS_INTERNAL(name) XS(name) #endif /* Now, finally, after all this mess, we want an ExtUtils::ParseXS * internal macro that we're free to redefine for varying linkage due * to the EXPORT_XSUB_SYMBOLS XS keyword. This is internal, use * XS_EXTERNAL(name) or XS_INTERNAL(name) in your code if you need to! */ #undef XS_EUPXS #if defined(PERL_EUPXS_ALWAYS_EXPORT) # define XS_EUPXS(name) XS_EXTERNAL(name) #else /* default to internal */ # define XS_EUPXS(name) XS_INTERNAL(name) #endif EOF print <<"EOF"; #ifndef PERL_ARGS_ASSERT_CROAK_XS_USAGE #define PERL_ARGS_ASSERT_CROAK_XS_USAGE assert(cv); assert(params) /* prototype to pass -Wmissing-prototypes */ STATIC void S_croak_xs_usage(const CV *const cv, const char *const params); STATIC void S_croak_xs_usage(const CV *const cv, const char *const params) { const GV *const gv = CvGV(cv); PERL_ARGS_ASSERT_CROAK_XS_USAGE; if (gv) { const char *const gvname = GvNAME(gv); const HV *const stash = GvSTASH(gv); const char *const hvname = stash ? HvNAME(stash) : NULL; if (hvname) Perl_croak_nocontext("Usage: %s::%s(%s)", hvname, gvname, params); else Perl_croak_nocontext("Usage: %s(%s)", gvname, params); } else { /* Pants. I don't think that it should be possible to get here. */ Perl_croak_nocontext("Usage: CODE(0x%" UVxf ")(%s)", PTR2UV(cv), params); } } #undef PERL_ARGS_ASSERT_CROAK_XS_USAGE #define croak_xs_usage S_croak_xs_usage #endif /* NOTE: the prototype of newXSproto() is different in versions of perls, * so we define a portable version of newXSproto() */ #ifdef newXS_flags #define newXSproto_portable(name, c_impl, file, proto) newXS_flags(name, c_impl, file, proto, 0) #else #define newXSproto_portable(name, c_impl, file, proto) (PL_Sv=(SV*)newXS(name, c_impl, file), sv_setpv(PL_Sv, proto), (CV*)PL_Sv) #endif /* !defined(newXS_flags) */ #if PERL_VERSION_LE(5, 21, 5) # define newXS_deffile(a,b) Perl_newXS(aTHX_ a,b,file) #else # define newXS_deffile(a,b) Perl_newXS_deffile(aTHX_ a,b) #endif EOF return 1; } sub assign_func_args { my ($self, $argsref, $class) = @_; my @func_args = @{$argsref}; shift @func_args if defined($class); for my $arg (@func_args) { $arg =~ s/^/&/ if $self->{in_out}->{$arg}; } return join(", ", @func_args); } sub analyze_preprocessor_statements { my ($self, $statement, $XSS_work_idx, $BootCode_ref) = @_; if ($statement eq 'if') { $XSS_work_idx = @{ $self->{XSStack} }; push(@{ $self->{XSStack} }, {type => 'if'}); } else { $self->death("Error: '$statement' with no matching 'if'") if $self->{XSStack}->[-1]{type} ne 'if'; if ($self->{XSStack}->[-1]{varname}) { push(@{ $self->{InitFileCode} }, "#endif\n"); push(@{ $BootCode_ref }, "#endif"); } my(@fns) = keys %{$self->{XSStack}->[-1]{functions}}; if ($statement ne 'endif') { # Hide the functions defined in other #if branches, and reset. @{$self->{XSStack}->[-1]{other_functions}}{@fns} = (1) x @fns; @{$self->{XSStack}->[-1]}{qw(varname functions)} = ('', {}); } else { my($tmp) = pop(@{ $self->{XSStack} }); 0 while (--$XSS_work_idx && $self->{XSStack}->[$XSS_work_idx]{type} ne 'if'); # Keep all new defined functions push(@fns, keys %{$tmp->{other_functions}}); @{$self->{XSStack}->[$XSS_work_idx]{functions}}{@fns} = (1) x @fns; } } return ($self, $XSS_work_idx, $BootCode_ref); } sub set_cond { my ($ellipsis, $min_args, $num_args) = @_; my $cond; if ($ellipsis) { $cond = ($min_args ? qq(items < $min_args) : 0); } elsif ($min_args == $num_args) { $cond = qq(items != $min_args); } else { $cond = qq(items < $min_args || items > $num_args); } return $cond; } sub current_line_number { my $self = shift; my $line_number = $self->{line_no}->[@{ $self->{line_no} } - @{ $self->{line} } -1]; return $line_number; } sub Warn { my $self = shift; my $warn_line_number = $self->current_line_number(); print STDERR "@_ in $self->{filename}, line $warn_line_number\n"; } sub blurt { my $self = shift; $self->Warn(@_); $self->{errors}++ } sub death { my $self = shift; $self->Warn(@_); exit 1; } sub check_conditional_preprocessor_statements { my ($self) = @_; my @cpp = grep(/^\#\s*(?:if|e\w+)/, @{ $self->{line} }); if (@cpp) { my $cpplevel; for my $cpp (@cpp) { if ($cpp =~ /^\#\s*if/) { $cpplevel++; } elsif (!$cpplevel) { $self->Warn("Warning: #else/elif/endif without #if in this function"); print STDERR " (precede it with a blank line if the matching #if is outside the function)\n" if $self->{XSStack}->[-1]{type} eq 'if'; return; } elsif ($cpp =~ /^\#\s*endif/) { $cpplevel--; } } $self->Warn("Warning: #if without #endif in this function") if $cpplevel; } } sub escape_file_for_line_directive { my $string = shift; $string =~ s/\\/\\\\/g; $string =~ s/"/\\"/g; return $string; } sub report_typemap_failure { my ($self, $tm, $ctype, $error_method) = @_; $error_method ||= 'blurt'; my @avail_ctypes = $tm->list_mapped_ctypes; my $err = "Could not find a typemap for C type '$ctype'.\n" . "The following C types are mapped by the current typemap:\n'" . join("', '", @avail_ctypes) . "'\n"; $self->$error_method($err); return(); } 1; # vim: ts=2 sw=2 et: package ExtUtils::Typemaps::Type; use 5.006001; use strict; use warnings; require ExtUtils::Typemaps; our $VERSION = '3.38'; sub new { my $prot = shift; my $class = ref($prot)||$prot; my %args = @_; if (!ref($prot)) { if (not defined $args{xstype} or not defined $args{ctype}) { die("Need xstype and ctype parameters"); } } my $self = bless( (ref($prot) ? {%$prot} : {proto => ''}) => $class ); $self->{xstype} = $args{xstype} if defined $args{xstype}; $self->{ctype} = $args{ctype} if defined $args{ctype}; $self->{tidy_ctype} = ExtUtils::Typemaps::tidy_type($self->{ctype}); $self->{proto} = $args{'prototype'} if defined $args{'prototype'}; return $self; } sub proto { $_[0]->{proto} = $_[1] if @_ > 1; return $_[0]->{proto}; } sub xstype { return $_[0]->{xstype}; } sub ctype { return defined($_[0]->{ctype}) ? $_[0]->{ctype} : $_[0]->{tidy_ctype}; } sub tidy_ctype { return $_[0]->{tidy_ctype}; } 1; package ExtUtils::Typemaps::OutputMap; use 5.006001; use strict; use warnings; our $VERSION = '3.38'; sub new { my $prot = shift; my $class = ref($prot)||$prot; my %args = @_; if (!ref($prot)) { if (not defined $args{xstype} or not defined $args{code}) { die("Need xstype and code parameters"); } } my $self = bless( (ref($prot) ? {%$prot} : {}) => $class ); $self->{xstype} = $args{xstype} if defined $args{xstype}; $self->{code} = $args{code} if defined $args{code}; $self->{code} =~ s/^(?=\S)/\t/mg; return $self; } sub code { $_[0]->{code} = $_[1] if @_ > 1; return $_[0]->{code}; } sub xstype { return $_[0]->{xstype}; } sub cleaned_code { my $self = shift; my $code = $self->code; # Move C pre-processor instructions to column 1 to be strictly ANSI # conformant. Some pre-processors are fussy about this. $code =~ s/^\s+#/#/mg; $code =~ s/\s*\z/\n/; return $code; } sub targetable { my $self = shift; return $self->{targetable} if exists $self->{targetable}; our $bal; # ()-balanced $bal = qr[ (?: (?>[^()]+) | \( (??{ $bal }) \) )* ]x; my $bal_no_comma = qr[ (?: (?>[^(),]+) | \( (??{ $bal }) \) )+ ]x; # matches variations on (SV*) my $sv_cast = qr[ (?: \( \s* SV \s* \* \s* \) \s* )? ]x; my $size = qr[ # Third arg (to setpvn) , \s* (??{ $bal }) ]xo; my $code = $self->code; # We can still bootstrap compile 're', because in code re.pm is # available to miniperl, and does not attempt to load the XS code. use re 'eval'; my ($type, $with_size, $arg, $sarg) = ($code =~ m[^ \s+ sv_set([iunp])v(n)? # Type, is_setpvn \s* \( \s* $sv_cast \$arg \s* , \s* ( $bal_no_comma ) # Set from ( $size )? # Possible sizeof set-from \s* \) \s* ; \s* $ ]xo ); my $rv = undef; if ($type) { $rv = { type => $type, with_size => $with_size, what => $arg, what_size => $sarg, }; } $self->{targetable} = $rv; return $rv; } 1; package ExtUtils::Typemaps::InputMap; use 5.006001; use strict; use warnings; our $VERSION = '3.38'; sub new { my $prot = shift; my $class = ref($prot)||$prot; my %args = @_; if (!ref($prot)) { if (not defined $args{xstype} or not defined $args{code}) { die("Need xstype and code parameters"); } } my $self = bless( (ref($prot) ? {%$prot} : {}) => $class ); $self->{xstype} = $args{xstype} if defined $args{xstype}; $self->{code} = $args{code} if defined $args{code}; $self->{code} =~ s/^(?=\S)/\t/mg; return $self; } sub code { $_[0]->{code} = $_[1] if @_ > 1; return $_[0]->{code}; } sub xstype { return $_[0]->{xstype}; } sub cleaned_code { my $self = shift; my $code = $self->code; $code =~ s/(?:;+\s*|;*\s+)\z//s; # Move C pre-processor instructions to column 1 to be strictly ANSI # conformant. Some pre-processors are fussy about this. $code =~ s/^\s+#/#/mg; $code =~ s/\s*\z/\n/; return $code; } 1; package ExtUtils::Typemaps::Cmd; use 5.006001; use strict; use warnings; our $VERSION = '3.38'; use ExtUtils::Typemaps; require Exporter; our @ISA = qw(Exporter); our @EXPORT = qw(embeddable_typemap); our %EXPORT_TAGS = (all => \@EXPORT); sub embeddable_typemap { my @tms = @_; # Get typemap objects my @tm_objs = map [$_, _intuit_typemap_source($_)], @tms; # merge or short-circuit my $final_tm; if (@tm_objs == 1) { # just one, merge would be pointless $final_tm = shift(@tm_objs)->[1]; } else { # multiple, need merge $final_tm = ExtUtils::Typemaps->new; foreach my $other_tm (@tm_objs) { my ($tm_ident, $tm_obj) = @$other_tm; eval { $final_tm->merge(typemap => $tm_obj); 1 } or do { my $err = $@ || 'Zombie error'; die "Failed to merge typ"; } } } # stringify for embedding return $final_tm->as_embedded_typemap(); } sub _load_module { my $name = shift; return eval "require $name; 1"; } SCOPE: { my %sources = ( module => sub { my $ident = shift; my $tm; if (/::/) { # looks like FQ module name, try that first foreach my $module ($ident, "ExtUtils::Typemaps::$ident") { if (_load_module($module)) { eval { $tm = $module->new } and return $tm; } } } else { foreach my $module ("ExtUtils::Typemaps::$ident", "$ident") { if (_load_module($module)) { eval { $tm = $module->new } and return $tm; } } } return(); }, file => sub { my $ident = shift; return unless -e $ident and -r _; return ExtUtils::Typemaps->new(file => $ident); }, ); # Try to find typemap either from module or file sub _intuit_typemap_source { my $identifier = shift; my @locate_attempts; if ($identifier =~ /::/ || $identifier !~ /[^\w_]/) { @locate_attempts = qw(module file); } else { @locate_attempts = qw(file module); } foreach my $source (@locate_attempts) { my $tm = $sources{$source}->($identifier); return $tm if defined $tm; } die "Unable to find typemap for '$identifier': " . "Tried to load both as file or module and failed.\n"; } } # end SCOPE 1; package ExtUtils::Liblist::Kid; # XXX Splitting this out into its own .pm is a temporary solution. # This kid package is to be used by MakeMaker. It will not work if # $self is not a Makemaker. use 5.006; # Broken out of MakeMaker from version 4.11 use strict; use warnings; our $VERSION = '7.34'; $VERSION = eval $VERSION; use ExtUtils::MakeMaker::Config; use Cwd 'cwd'; use File::Basename; use File::Spec; sub ext { if ( $^O eq 'VMS' ) { return &_vms_ext; } elsif ( $^O eq 'MSWin32' ) { return &_win32_ext; } else { return &_unix_os2_ext; } } sub _unix_os2_ext { my ( $self, $potential_libs, $verbose, $give_libs ) = @_; $verbose ||= 0; if ( $^O =~ /os2|android/ and $Config{perllibs} ) { # Dynamic libraries are not transitive, so we may need including # the libraries linked against perl.dll/libperl.so again. $potential_libs .= " " if $potential_libs; $potential_libs .= $Config{perllibs}; } return ( "", "", "", "", ( $give_libs ? [] : () ) ) unless $potential_libs; warn "Potential libraries are '$potential_libs':\n" if $verbose; my ( $so ) = $Config{so}; my ( $libs ) = defined $Config{perllibs} ? $Config{perllibs} : $Config{libs}; my $Config_libext = $Config{lib_ext} || ".a"; my $Config_dlext = $Config{dlext}; # compute $extralibs, $bsloadlibs and $ldloadlibs from # $potential_libs # this is a rewrite of Andy Dougherty's extliblist in perl my ( @searchpath ); # from "-L/path" entries in $potential_libs my ( @libpath ) = split " ", $Config{'libpth'} || ''; my ( @ldloadlibs, @bsloadlibs, @extralibs, @ld_run_path, %ld_run_path_seen ); my ( @libs, %libs_seen ); my ( $fullname, @fullname ); my ( $pwd ) = cwd(); # from Cwd.pm my ( $found ) = 0; if ( $^O eq 'darwin' or $^O eq 'next' ) { # 'escape' Mach-O ld -framework and -F flags, so they aren't dropped later on $potential_libs =~ s/(^|\s)(-(?:weak_|reexport_|lazy_)?framework)\s+(\S+)/$1-Wl,$2 -Wl,$3/g; $potential_libs =~ s/(^|\s)(-F)\s*(\S+)/$1-Wl,$2 -Wl,$3/g; } foreach my $thislib ( split ' ', $potential_libs ) { my ( $custom_name ) = ''; # Handle possible linker path arguments. if ( $thislib =~ s/^(-[LR]|-Wl,-R|-Wl,-rpath,)// ) { # save path flag type my ( $ptype ) = $1; unless ( -d $thislib ) { warn "$ptype$thislib ignored, directory does not exist\n" if $verbose; next; } my ( $rtype ) = $ptype; if ( ( $ptype eq '-R' ) or ( $ptype =~ m!^-Wl,-[Rr]! ) ) { if ( $Config{'lddlflags'} =~ /-Wl,-[Rr]/ ) { $rtype = '-Wl,-R'; } elsif ( $Config{'lddlflags'} =~ /-R/ ) { $rtype = '-R'; } } unless ( File::Spec->file_name_is_absolute( $thislib ) ) { warn "Warning: $ptype$thislib changed to $ptype$pwd/$thislib\n"; $thislib = $self->catdir( $pwd, $thislib ); } push( @searchpath, $thislib ); push( @extralibs, "$ptype$thislib" ); push( @ldloadlibs, "$rtype$thislib" ); next; } if ( $thislib =~ m!^-Wl,! ) { push( @extralibs, $thislib ); push( @ldloadlibs, $thislib ); next; } # Handle possible library arguments. if ( $thislib =~ s/^-l(:)?// ) { # Handle -l:foo.so, which means that the library will # actually be called foo.so, not libfoo.so. This # is used in Android by ExtUtils::Depends to allow one XS # module to link to another. $custom_name = $1 || ''; } else { warn "Unrecognized argument in LIBS ignored: '$thislib'\n"; next; } my ( $found_lib ) = 0; foreach my $thispth ( @searchpath, @libpath ) { # Try to find the full name of the library. We need this to # determine whether it's a dynamically-loadable library or not. # This tends to be subject to various os-specific quirks. # For gcc-2.6.2 on linux (March 1995), DLD can not load # .sa libraries, with the exception of libm.sa, so we # deliberately skip them. if ((@fullname = $self->lsdir($thispth, "^\Qlib$thislib.$so.\E[0-9]+")) || (@fullname = $self->lsdir($thispth, "^\Qlib$thislib.\E[0-9]+\Q\.$so"))) { # Take care that libfoo.so.10 wins against libfoo.so.9. # Compare two libraries to find the most recent version # number. E.g. if you have libfoo.so.9.0.7 and # libfoo.so.10.1, first convert all digits into two # decimal places. Then we'll add ".00" to the shorter # strings so that we're comparing strings of equal length # Thus we'll compare libfoo.so.09.07.00 with # libfoo.so.10.01.00. Some libraries might have letters # in the version. We don't know what they mean, but will # try to skip them gracefully -- we'll set any letter to # '0'. Finally, sort in reverse so we can take the # first element. #TODO: iterate through the directory instead of sorting $fullname = "$thispth/" . ( sort { my ( $ma ) = $a; my ( $mb ) = $b; $ma =~ tr/A-Za-z/0/s; $ma =~ s/\b(\d)\b/0$1/g; $mb =~ tr/A-Za-z/0/s; $mb =~ s/\b(\d)\b/0$1/g; while ( length( $ma ) < length( $mb ) ) { $ma .= ".00"; } while ( length( $mb ) < length( $ma ) ) { $mb .= ".00"; } # Comparison deliberately backwards $mb cmp $ma; } @fullname )[0]; } elsif ( -f ( $fullname = "$thispth/lib$thislib.$so" ) && ( ( $Config{'dlsrc'} ne "dl_dld.xs" ) || ( $thislib eq "m" ) ) ) { } elsif (-f ( $fullname = "$thispth/lib${thislib}_s$Config_libext" ) && ( $Config{'archname'} !~ /RM\d\d\d-svr4/ ) && ( $thislib .= "_s" ) ) { # we must explicitly use _s version } elsif ( -f ( $fullname = "$thispth/lib$thislib$Config_libext" ) ) { } elsif ( defined( $Config_dlext ) && -f ( $fullname = "$thispth/lib$thislib.$Config_dlext" ) ) { } elsif ( -f ( $fullname = "$thispth/$thislib$Config_libext" ) ) { } elsif ( -f ( $fullname = "$thispth/lib$thislib.dll$Config_libext" ) ) { } elsif ( $^O eq 'cygwin' && -f ( $fullname = "$thispth/$thislib.dll" ) ) { } elsif ( -f ( $fullname = "$thispth/Slib$thislib$Config_libext" ) ) { } elsif ($^O eq 'dgux' && -l ( $fullname = "$thispth/lib$thislib$Config_libext" ) && readlink( $fullname ) =~ /^elink:/s ) { # Some of DG's libraries look like misconnected symbolic # links, but development tools can follow them. (They # look like this: # # libm.a -> elink:${SDE_PATH:-/usr}/sde/\ # ${TARGET_BINARY_INTERFACE:-m88kdgux}/usr/lib/libm.a # # , the compilation tools expand the environment variables.) } elsif ( $custom_name && -f ( $fullname = "$thispth/$thislib" ) ) { } else { warn "$thislib not found in $thispth\n" if $verbose; next; } warn "'-l$thislib' found at $fullname\n" if $verbose; push @libs, $fullname unless $libs_seen{$fullname}++; $found++; $found_lib++; # Now update library lists # what do we know about this library... my $is_dyna = ( $fullname !~ /\Q$Config_libext\E\z/ ); my $in_perl = ( $libs =~ /\B-l:?\Q${thislib}\E\b/s ); # include the path to the lib once in the dynamic linker path # but only if it is a dynamic lib and not in Perl itself my ( $fullnamedir ) = dirname( $fullname ); push @ld_run_path, $fullnamedir if $is_dyna && !$in_perl && !$ld_run_path_seen{$fullnamedir}++; # Do not add it into the list if it is already linked in # with the main perl executable. # We have to special-case the NeXT, because math and ndbm # are both in libsys_s unless ( $in_perl || ( $Config{'osname'} eq 'next' && ( $thislib eq 'm' || $thislib eq 'ndbm' ) ) ) { push( @extralibs, "-l$custom_name$thislib" ); } # We might be able to load this archive file dynamically if ( ( $Config{'dlsrc'} =~ /dl_next/ && $Config{'osvers'} lt '4_0' ) || ( $Config{'dlsrc'} =~ /dl_dld/ ) ) { # We push -l$thislib instead of $fullname because # it avoids hardwiring a fixed path into the .bs file. # Mkbootstrap will automatically add dl_findfile() to # the .bs file if it sees a name in the -l format. # USE THIS, when dl_findfile() is fixed: # push(@bsloadlibs, "-l$thislib"); # OLD USE WAS while checking results against old_extliblist push( @bsloadlibs, "$fullname" ); } else { if ( $is_dyna ) { # For SunOS4, do not add in this shared library if # it is already linked in the main perl executable push( @ldloadlibs, "-l$custom_name$thislib" ) unless ( $in_perl and $^O eq 'sunos' ); } else { push( @ldloadlibs, "-l$custom_name$thislib" ); } } last; # found one here so don't bother looking further } warn "Warning (mostly harmless): " . "No library found for -l$thislib\n" unless $found_lib > 0; } unless ( $found ) { return ( '', '', '', '', ( $give_libs ? \@libs : () ) ); } else { return ( "@extralibs", "@bsloadlibs", "@ldloadlibs", join( ":", @ld_run_path ), ( $give_libs ? \@libs : () ) ); } } sub _win32_ext { require Text::ParseWords; my ( $self, $potential_libs, $verbose, $give_libs ) = @_; $verbose ||= 0; # If user did not supply a list, we punt. # (caller should probably use the list in $Config{libs}) return ( "", "", "", "", ( $give_libs ? [] : () ) ) unless $potential_libs; # TODO: make this use MM_Win32.pm's compiler detection my %libs_seen; my @extralibs; my $cc = $Config{cc} || ''; my $VC = $cc =~ /\bcl\b/i; my $GC = $cc =~ /\bgcc\b/i; my $libext = _win32_lib_extensions(); my @searchpath = ( '' ); # from "-L/path" entries in $potential_libs my @libpath = _win32_default_search_paths( $VC, $GC ); my $pwd = cwd(); # from Cwd.pm my $search = 1; # compute @extralibs from $potential_libs my @lib_search_list = _win32_make_lib_search_list( $potential_libs, $verbose ); for ( @lib_search_list ) { my $thislib = $_; # see if entry is a flag if ( /^:\w+$/ ) { $search = 0 if lc eq ':nosearch'; $search = 1 if lc eq ':search'; _debug( "Ignoring unknown flag '$thislib'\n", $verbose ) if !/^:(no)?(search|default)$/i; next; } # if searching is disabled, do compiler-specific translations unless ( $search ) { s/^-l(.+)$/$1.lib/ unless $GC; s/^-L/-libpath:/ if $VC; push( @extralibs, $_ ); next; } # handle possible linker path arguments if ( s/^-L// and not -d ) { _debug( "$thislib ignored, directory does not exist\n", $verbose ); next; } elsif ( -d ) { unless ( File::Spec->file_name_is_absolute( $_ ) ) { warn "Warning: '$thislib' changed to '-L$pwd/$_'\n"; $_ = $self->catdir( $pwd, $_ ); } push( @searchpath, $_ ); next; } my @paths = ( @searchpath, @libpath ); my ( $fullname, $path ) = _win32_search_file( $thislib, $libext, \@paths, $verbose, $GC ); if ( !$fullname ) { warn "Warning (mostly harmless): No library found for $thislib\n"; next; } _debug( "'$thislib' found as '$fullname'\n", $verbose ); push( @extralibs, $fullname ); $libs_seen{$fullname} = 1 if $path; # why is this a special case? } my @libs = sort keys %libs_seen; return ( '', '', '', '', ( $give_libs ? \@libs : () ) ) unless @extralibs; # make sure paths with spaces are properly quoted @extralibs = map { qq["$_"] } @extralibs; @libs = map { qq["$_"] } @libs; my $lib = join( ' ', @extralibs ); # normalize back to backward slashes (to help braindead tools) # XXX this may break equally braindead GNU tools that don't understand # backslashes, either. Seems like one can't win here. Cursed be CP/M. $lib =~ s,/,\\,g; _debug( "Result: $lib\n", $verbose ); wantarray ? ( $lib, '', $lib, '', ( $give_libs ? \@libs : () ) ) : $lib; } sub _win32_make_lib_search_list { my ( $potential_libs, $verbose ) = @_; # If Config.pm defines a set of default libs, we always # tack them on to the user-supplied list, unless the user # specified :nodefault my $libs = $Config{'perllibs'}; $potential_libs = join( ' ', $potential_libs, $libs ) if $libs and $potential_libs !~ /:nodefault/i; _debug( "Potential libraries are '$potential_libs':\n", $verbose ); $potential_libs =~ s,\\,/,g; # normalize to forward slashes my @list = Text::ParseWords::quotewords( '\s+', 0, $potential_libs ); return @list; } sub _win32_default_search_paths { my ( $VC, $GC ) = @_; my $libpth = $Config{'libpth'} || ''; $libpth =~ s,\\,/,g; # normalize to forward slashes my @libpath = Text::ParseWords::quotewords( '\s+', 0, $libpth ); push @libpath, "$Config{installarchlib}/CORE"; # add "$Config{installarchlib}/CORE" to default search path push @libpath, split /;/, $ENV{LIB} if $VC and $ENV{LIB}; push @libpath, split /;/, $ENV{LIBRARY_PATH} if $GC and $ENV{LIBRARY_PATH}; return @libpath; } sub _win32_search_file { my ( $thislib, $libext, $paths, $verbose, $GC ) = @_; my @file_list = _win32_build_file_list( $thislib, $GC, $libext ); for my $lib_file ( @file_list ) { for my $path ( @{$paths} ) { my $fullname = $lib_file; $fullname = "$path\\$fullname" if $path; return ( $fullname, $path ) if -f $fullname; _debug( "'$thislib' not found as '$fullname'\n", $verbose ); } } return; } sub _win32_build_file_list { my ( $lib, $GC, $extensions ) = @_; my @pre_fixed = _win32_build_prefixed_list( $lib, $GC ); return map _win32_attach_extensions( $_, $extensions ), @pre_fixed; } sub _win32_build_prefixed_list { my ( $lib, $GC ) = @_; return $lib if $lib !~ s/^-l//; return $lib if $lib =~ /^lib/ and !$GC; ( my $no_prefix = $lib ) =~ s/^lib//i; $lib = "lib$lib" if $no_prefix eq $lib; return ( $lib, $no_prefix ) if $GC; return ( $no_prefix, $lib ); } sub _win32_attach_extensions { my ( $lib, $extensions ) = @_; return map _win32_try_attach_extension( $lib, $_ ), @{$extensions}; } sub _win32_try_attach_extension { my ( $lib, $extension ) = @_; return $lib if $lib =~ /\Q$extension\E$/i; return "$lib$extension"; } sub _win32_lib_extensions { my @extensions; push @extensions, $Config{'lib_ext'} if $Config{'lib_ext'}; push @extensions, '.dll.a' if grep { m!^\.a$! } @extensions; push @extensions, '.lib' unless grep { m!^\.lib$! } @extensions; return \@extensions; } sub _debug { my ( $message, $verbose ) = @_; return if !$verbose; warn $message; return; } sub _vms_ext { my ( $self, $potential_libs, $verbose, $give_libs ) = @_; $verbose ||= 0; my ( @crtls, $crtlstr ); @crtls = ( ( $Config{'ldflags'} =~ m-/Debug-i ? $Config{'dbgprefix'} : '' ) . 'PerlShr/Share' ); push( @crtls, grep { not /\(/ } split /\s+/, $Config{'perllibs'} ); push( @crtls, grep { not /\(/ } split /\s+/, $Config{'libc'} ); # In general, we pass through the basic libraries from %Config unchanged. # The one exception is that if we're building in the Perl source tree, and # a library spec could be resolved via a logical name, we go to some trouble # to insure that the copy in the local tree is used, rather than one to # which a system-wide logical may point. if ( $self->{PERL_SRC} ) { my ( $locspec, $type ); foreach my $lib ( @crtls ) { if ( ( $locspec, $type ) = $lib =~ m{^([\w\$-]+)(/\w+)?} and $locspec =~ /perl/i ) { if ( lc $type eq '/share' ) { $locspec .= $Config{'exe_ext'}; } elsif ( lc $type eq '/library' ) { $locspec .= $Config{'lib_ext'}; } else { $locspec .= $Config{'obj_ext'}; } $locspec = $self->catfile( $self->{PERL_SRC}, $locspec ); $lib = "$locspec$type" if -e $locspec; } } } $crtlstr = @crtls ? join( ' ', @crtls ) : ''; unless ( $potential_libs ) { warn "Result:\n\tEXTRALIBS: \n\tLDLOADLIBS: $crtlstr\n" if $verbose; return ( '', '', $crtlstr, '', ( $give_libs ? [] : () ) ); } my ( %found, @fndlibs, $ldlib ); my $cwd = cwd(); my ( $so, $lib_ext, $obj_ext ) = @Config{ 'so', 'lib_ext', 'obj_ext' }; # List of common Unix library names and their VMS equivalents # (VMS equivalent of '' indicates that the library is automatically # searched by the linker, and should be skipped here.) my ( @flibs, %libs_seen ); my %libmap = ( 'm' => '', 'f77' => '', 'F77' => '', 'V77' => '', 'c' => '', 'malloc' => '', 'crypt' => '', 'resolv' => '', 'c_s' => '', 'socket' => '', 'X11' => 'DECW$XLIBSHR', 'Xt' => 'DECW$XTSHR', 'Xm' => 'DECW$XMLIBSHR', 'Xmu' => 'DECW$XMULIBSHR' ); warn "Potential libraries are '$potential_libs'\n" if $verbose; # First, sort out directories and library names in the input my ( @dirs, @libs ); foreach my $lib ( split ' ', $potential_libs ) { push( @dirs, $1 ), next if $lib =~ /^-L(.*)/; push( @dirs, $lib ), next if $lib =~ /[:>\]]$/; push( @dirs, $lib ), next if -d $lib; push( @libs, $1 ), next if $lib =~ /^-l(.*)/; push( @libs, $lib ); } push( @dirs, split( ' ', $Config{'libpth'} ) ); # Now make sure we've got VMS-syntax absolute directory specs # (We don't, however, check whether someone's hidden a relative # path in a logical name.) foreach my $dir ( @dirs ) { unless ( -d $dir ) { warn "Skipping nonexistent Directory $dir\n" if $verbose > 1; $dir = ''; next; } warn "Resolving directory $dir\n" if $verbose; if ( File::Spec->file_name_is_absolute( $dir ) ) { $dir = VMS::Filespec::vmspath( $dir ); } else { $dir = $self->catdir( $cwd, $dir ); } } @dirs = grep { length( $_ ) } @dirs; unshift( @dirs, '' ); # Check each $lib without additions first LIB: foreach my $lib ( @libs ) { if ( exists $libmap{$lib} ) { next unless length $libmap{$lib}; $lib = $libmap{$lib}; } my ( @variants, $cand ); my ( $ctype ) = ''; # If we don't have a file type, consider it a possibly abbreviated name and # check for common variants. We try these first to grab libraries before # a like-named executable image (e.g. -lperl resolves to perlshr.exe # before perl.exe). if ( $lib !~ /\.[^:>\]]*$/ ) { push( @variants, "${lib}shr", "${lib}rtl", "${lib}lib" ); push( @variants, "lib$lib" ) if $lib !~ /[:>\]]/; } push( @variants, $lib ); warn "Looking for $lib\n" if $verbose; foreach my $variant ( @variants ) { my ( $fullname, $name ); foreach my $dir ( @dirs ) { my ( $type ); $name = "$dir$variant"; warn "\tChecking $name\n" if $verbose > 2; $fullname = VMS::Filespec::rmsexpand( $name ); if ( defined $fullname and -f $fullname ) { # It's got its own suffix, so we'll have to figure out the type if ( $fullname =~ /(?:$so|exe)$/i ) { $type = 'SHR'; } elsif ( $fullname =~ /(?:$lib_ext|olb)$/i ) { $type = 'OLB'; } elsif ( $fullname =~ /(?:$obj_ext|obj)$/i ) { warn "Warning (mostly harmless): " . "Plain object file $fullname found in library list\n"; $type = 'OBJ'; } else { warn "Warning (mostly harmless): " . "Unknown library type for $fullname; assuming shared\n"; $type = 'SHR'; } } elsif (-f ( $fullname = VMS::Filespec::rmsexpand( $name, $so ) ) or -f ( $fullname = VMS::Filespec::rmsexpand( $name, '.exe' ) ) ) { $type = 'SHR'; $name = $fullname unless $fullname =~ /exe;?\d*$/i; } elsif ( not length( $ctype ) and # If we've got a lib already, # don't bother ( -f ( $fullname = VMS::Filespec::rmsexpand( $name, $lib_ext ) ) or -f ( $fullname = VMS::Filespec::rmsexpand( $name, '.olb' ) ) ) ) { $type = 'OLB'; $name = $fullname unless $fullname =~ /olb;?\d*$/i; } elsif ( not length( $ctype ) and # If we've got a lib already, # don't bother ( -f ( $fullname = VMS::Filespec::rmsexpand( $name, $obj_ext ) ) or -f ( $fullname = VMS::Filespec::rmsexpand( $name, '.obj' ) ) ) ) { warn "Warning (mostly harmless): " . "Plain object file $fullname found in library list\n"; $type = 'OBJ'; $name = $fullname unless $fullname =~ /obj;?\d*$/i; } if ( defined $type ) { $ctype = $type; $cand = $name; last if $ctype eq 'SHR'; } } if ( $ctype ) { push @{ $found{$ctype} }, $cand; warn "\tFound as $cand (really $fullname), type $ctype\n" if $verbose > 1; push @flibs, $name unless $libs_seen{$fullname}++; next LIB; } } warn "Warning (mostly harmless): " . "No library found for $lib\n"; } push @fndlibs, @{ $found{OBJ} } if exists $found{OBJ}; push @fndlibs, map { "$_/Library" } @{ $found{OLB} } if exists $found{OLB}; push @fndlibs, map { "$_/Share" } @{ $found{SHR} } if exists $found{SHR}; my $lib = join( ' ', @fndlibs ); $ldlib = $crtlstr ? "$lib $crtlstr" : $lib; $ldlib =~ s/^\s+|\s+$//g; warn "Result:\n\tEXTRALIBS: $lib\n\tLDLOADLIBS: $ldlib\n" if $verbose; wantarray ? ( $lib, '', $ldlib, '', ( $give_libs ? \@flibs : () ) ) : $lib; } 1; package Filter::Simple; use Text::Balanced ':ALL'; our $VERSION = '0.95'; use Filter::Util::Call; use Carp; our @EXPORT = qw( FILTER FILTER_ONLY ); sub import { if (@_>1) { shift; goto &FILTER } else { *{caller()."::$_"} = \&$_ foreach @EXPORT } } sub fail { croak "FILTER_ONLY: ", @_; } my $exql = sub { my @bits = extract_quotelike $_[0], qr//; return unless $bits[0]; return \@bits; }; my $ncws = qr/\s+/; my $comment = qr/(?()-]|\^[A-Z]?)\} | (?:\$#?|[*\@\%]|\\&)\$*\s* (?: \{\s*(?:\^(?=[A-Z_]))?(?:\w|::|'\w)*\s*\} | (?:\^(?=[A-Z_]))?(?:\w|::|'\w)* | (?=\{) # ${ block } ) ) | \$\s*(?!::)(?:\d+|[][&`'#+*./|,";%=~:?!\@<>()-]|\^[A-Z]?) }x; my %extractor_for = ( quotelike => [ $ws, $variable, $id, { MATCH => \&extract_quotelike } ], regex => [ $ws, $pod_or_DATA, $id, $exql ], string => [ $ws, $pod_or_DATA, $id, $exql ], code => [ $ws, { DONT_MATCH => $pod_or_DATA }, $variable, $id, { DONT_MATCH => \&extract_quotelike } ], code_no_comments => [ { DONT_MATCH => $comment }, $ncws, { DONT_MATCH => $pod_or_DATA }, $variable, $id, { DONT_MATCH => \&extract_quotelike } ], executable => [ $ws, { DONT_MATCH => $pod_or_DATA } ], executable_no_comments => [ { DONT_MATCH => $comment }, $ncws, { DONT_MATCH => $pod_or_DATA } ], all => [ { MATCH => qr/(?s:.*)/ } ], ); my %selector_for = ( all => sub { my ($t)=@_; sub{ $_=$$_; $t->(@_); $_} }, executable=> sub { my ($t)=@_; sub{ref() ? $_=$$_ : $t->(@_); $_} }, quotelike => sub { my ($t)=@_; sub{ref() && do{$_=$$_; $t->(@_)}; $_} }, regex => sub { my ($t)=@_; sub{ref() or return $_; my ($ql,undef,$pre,$op,$ld,$pat) = @$_; return $_->[0] unless $op =~ /^(qr|m|s)/ || !$op && ($ld eq '/' || $ld eq '?'); $_ = $pat; $t->(@_); $ql =~ s/^(\s*\Q$op\E\s*\Q$ld\E)\Q$pat\E/$1$_/; return "$pre$ql"; }; }, string => sub { my ($t)=@_; sub{ref() or return $_; local *args = \@_; my ($pre,$op,$ld1,$str1,$rd1,$ld2,$str2,$rd2,$flg) = @{$_}[2..10]; return $_->[0] if $op =~ /^(qr|m)/ || !$op && ($ld1 eq '/' || $ld1 eq '?'); if (!$op || $op eq 'tr' || $op eq 'y') { local *_ = \$str1; $t->(@args); } if ($op =~ /^(tr|y|s)/) { local *_ = \$str2; $t->(@args); } my $result = "$pre$op$ld1$str1$rd1"; $result .= $ld2 if $ld1 =~ m/[[({<]/; #])}> $result .= "$str2$rd2$flg"; return $result; }; }, ); sub gen_std_filter_for { my ($type, $transform) = @_; return sub { my $instr; local @components; for (extract_multiple($_,$extractor_for{$type})) { if (ref()) { push @components, $_; $instr=0 } elsif ($instr) { $components[-1] .= $_ } else { push @components, $_; $instr=1 } } if ($type =~ /^code/) { my $count = 0; local $placeholder = qr/\Q$;\E(.{4})\Q$;\E/s; my $extractor = qr/\Q$;\E(.{4})\Q$;\E/s; $_ = join "", map { ref $_ ? $;.pack('N',$count++).$; : $_ } @components; @components = grep { ref $_ } @components; $transform->(@_); s/$extractor/${$components[unpack('N',$1)]}/g; } else { my $selector = $selector_for{$type}->($transform); $_ = join "", map $selector->(@_), @components; } } }; sub FILTER (&;$) { my $caller = caller; my ($filter, $terminator) = @_; no warnings 'redefine'; *{"${caller}::import"} = gen_filter_import($caller,$filter,$terminator); *{"${caller}::unimport"} = gen_filter_unimport($caller); } sub FILTER_ONLY { my $caller = caller; while (@_ > 1) { my ($what, $how) = splice(@_, 0, 2); fail "Unknown selector: $what" unless exists $extractor_for{$what}; fail "Filter for $what is not a subroutine reference" unless ref $how eq 'CODE'; push @transforms, gen_std_filter_for($what,$how); } my $terminator = shift; my $multitransform = sub { foreach my $transform ( @transforms ) { $transform->(@_); } }; no warnings 'redefine'; *{"${caller}::import"} = gen_filter_import($caller,$multitransform,$terminator); *{"${caller}::unimport"} = gen_filter_unimport($caller); } my $ows = qr/(?:[ \t]+|#[^\n]*)*/; sub gen_filter_import { my ($class, $filter, $terminator) = @_; my %terminator; my $prev_import = *{$class."::import"}{CODE}; return sub { my ($imported_class, @args) = @_; my $def_terminator = qr/^(?:\s*no\s+$imported_class\s*;$ows|__(?:END|DATA)__)\r?$/; if (!defined $terminator) { $terminator{terminator} = $def_terminator; } elsif (!ref $terminator || ref $terminator eq 'Regexp') { $terminator{terminator} = $terminator; } elsif (ref $terminator ne 'HASH') { croak "Terminator must be specified as scalar or hash ref" } elsif (!exists $terminator->{terminator}) { $terminator{terminator} = $def_terminator; } filter_add( sub { my ($status, $lastline); my $count = 0; my $data = ""; while ($status = filter_read()) { return $status if $status < 0; if ($terminator{terminator} && m/$terminator{terminator}/) { $lastline = $_; $count++; last; } $data .= $_; $count++; $_ = ""; } return $count if not $count; $_ = $data; $filter->($imported_class, @args) unless $status < 0; if (defined $lastline) { if (defined $terminator{becomes}) { $_ .= $terminator{becomes}; } elsif ($lastline =~ $def_terminator) { $_ .= $lastline; } } return $count; } ); if ($prev_import) { goto &$prev_import; } elsif ($class->isa('Exporter')) { $class->export_to_level(1,@_); } } } sub gen_filter_unimport { my ($class) = @_; return sub { filter_del(); goto &$prev_unimport if $prev_unimport; } } 1; __END__ # B::Deparse.pm # Copyright (c) 1998-2000, 2002, 2003, 2004, 2005, 2006 Stephen McCamant. # All rights reserved. # This module is free software; you can redistribute and/or modify # it under the same terms as Perl itself. # This is based on the module of the same name by Malcolm Beattie, # but essentially none of his code remains. package B::Deparse; use Carp; use B qw(class main_root main_start main_cv svref_2object opnumber perlstring OPf_WANT OPf_WANT_VOID OPf_WANT_SCALAR OPf_WANT_LIST OPf_KIDS OPf_REF OPf_STACKED OPf_SPECIAL OPf_MOD OPf_PARENS OPpLVAL_INTRO OPpOUR_INTRO OPpENTERSUB_AMPER OPpSLICE OPpKVSLICE OPpCONST_BARE OPpTRANS_SQUASH OPpTRANS_DELETE OPpTRANS_COMPLEMENT OPpTARGET_MY OPpEXISTS_SUB OPpSORT_NUMERIC OPpSORT_INTEGER OPpREPEAT_DOLIST OPpSORT_REVERSE OPpMULTIDEREF_EXISTS OPpMULTIDEREF_DELETE OPpSPLIT_ASSIGN OPpSPLIT_LEX OPpPADHV_ISKEYS OPpRV2HV_ISKEYS OPpCONCAT_NESTED OPpMULTICONCAT_APPEND OPpMULTICONCAT_STRINGIFY OPpMULTICONCAT_FAKE OPpTRUEBOOL OPpINDEX_BOOLNEG SVf_IOK SVf_NOK SVf_ROK SVf_POK SVpad_OUR SVf_FAKE SVs_RMG SVs_SMG SVs_PADTMP SVpad_TYPED CVf_METHOD CVf_LVALUE PMf_KEEP PMf_GLOBAL PMf_CONTINUE PMf_EVAL PMf_ONCE PMf_MULTILINE PMf_SINGLELINE PMf_FOLD PMf_EXTENDED PMf_EXTENDED_MORE PADNAMEt_OUTER MDEREF_reload MDEREF_AV_pop_rv2av_aelem MDEREF_AV_gvsv_vivify_rv2av_aelem MDEREF_AV_padsv_vivify_rv2av_aelem MDEREF_AV_vivify_rv2av_aelem MDEREF_AV_padav_aelem MDEREF_AV_gvav_aelem MDEREF_HV_pop_rv2hv_helem MDEREF_HV_gvsv_vivify_rv2hv_helem MDEREF_HV_padsv_vivify_rv2hv_helem MDEREF_HV_vivify_rv2hv_helem MDEREF_HV_padhv_helem MDEREF_HV_gvhv_helem MDEREF_ACTION_MASK MDEREF_INDEX_none MDEREF_INDEX_const MDEREF_INDEX_padsv MDEREF_INDEX_gvsv MDEREF_INDEX_MASK MDEREF_FLAG_last MDEREF_MASK MDEREF_SHIFT ); $VERSION = '1.48'; use strict; our $AUTOLOAD; use warnings (); require feature; use Config; BEGIN { # List version-specific constants here. # Easiest way to keep this code portable between version looks to # be to fake up a dummy constant that will never actually be true. foreach (qw(OPpSORT_INPLACE OPpSORT_DESCEND OPpITER_REVERSED OPpCONST_NOVER OPpPAD_STATE PMf_SKIPWHITE RXf_SKIPWHITE PMf_CHARSET PMf_KEEPCOPY PMf_NOCAPTURE CVf_ANONCONST CVf_LOCKED OPpREVERSE_INPLACE OPpSUBSTR_REPL_FIRST PMf_NONDESTRUCT OPpCONST_ARYBASE OPpEVAL_BYTES OPpLVREF_TYPE OPpLVREF_SV OPpLVREF_AV OPpLVREF_HV OPpLVREF_CV OPpLVREF_ELEM SVpad_STATE)) { eval { B->import($_) }; no strict 'refs'; *{$_} = sub () {0} unless *{$_}{CODE}; } } # Todo: # (See also BUGS section at the end of this file) # # - finish tr/// changes # - add option for even more parens (generalize \&foo change) # - left/right context # - copy comments (look at real text with $^P?) # - avoid semis in one-statement blocks # - associativity of &&=, ||=, ?: # - ',' => '=>' (auto-unquote?) # - break long lines ("\r" as discretionary break?) # - configurable syntax highlighting: ANSI color, HTML, TeX, etc. # - more style options: brace style, hex vs. octal, quotes, ... # - print big ints as hex/octal instead of decimal (heuristic?) # - handle 'my $x if 0'? # - version using op_next instead of op_first/sibling? # - avoid string copies (pass arrays, one big join?) # - here-docs? # Current test.deparse failures # comp/hints 6 - location of BEGIN blocks wrt. block openings # run/switchI 1 - missing -I switches entirely # perl -Ifoo -e 'print @INC' # op/caller 2 - warning mask propagates backwards before warnings::register # 'use warnings; BEGIN {${^WARNING_BITS} eq "U"x12;} use warnings::register' # op/getpid 2 - can't assign to shared my() declaration (threads only) # 'my $x : shared = 5' # op/override 7 - parens on overridden require change v-string interpretation # 'BEGIN{*CORE::GLOBAL::require=sub {}} require v5.6' # c.f. 'BEGIN { *f = sub {0} }; f 2' # op/pat 774 - losing Unicode-ness of Latin1-only strings # 'use charnames ":short"; $x="\N{latin:a with acute}"' # op/recurse 12 - missing parens on recursive call makes it look like method # 'sub f { f($x) }' # op/subst 90 - inconsistent handling of utf8 under "use utf8" # op/taint 29 - "use re 'taint'" deparsed in the wrong place wrt. block open # op/tiehandle compile - "use strict" deparsed in the wrong place # uni/tr_ several # ext/B/t/xref 11 - line numbers when we add newlines to one-line subs # ext/Data/Dumper/t/dumper compile # ext/DB_file/several # ext/Encode/several # ext/Ernno/Errno warnings # ext/IO/lib/IO/t/io_sel 23 # ext/PerlIO/t/encoding compile # ext/POSIX/t/posix 6 # ext/Socket/Socket 8 # ext/Storable/t/croak compile # lib/Attribute/Handlers/t/multi compile # lib/bignum/ several # lib/charnames 35 # lib/constant 32 # lib/English 40 # lib/ExtUtils/t/bytes 4 # lib/File/DosGlob compile # lib/Filter/Simple/t/data 1 # lib/Math/BigInt/t/constant 1 # lib/Net/t/config Deparse-warning # lib/overload compile # lib/Switch/ several # lib/Symbol 4 # lib/Test/Simple several # lib/Term/Complete # lib/Tie/File/t/29_downcopy 5 # lib/vars 22 # Object fields: # # in_coderef2text: # True when deparsing via $deparse->coderef2text; false when deparsing the # main program. # # avoid_local: # (local($a), local($b)) and local($a, $b) have the same internal # representation but the short form looks better. We notice we can # use a large-scale local when checking the list, but need to prevent # individual locals too. This hash holds the addresses of OPs that # have already had their local-ness accounted for. The same thing # is done with my(). # # curcv: # CV for current sub (or main program) being deparsed # # curcvlex: # Cached hash of lexical variables for curcv: keys are # names prefixed with "m" or "o" (representing my/our), and # each value is an array with two elements indicating the cop_seq # of scopes in which a var of that name is valid and a third ele- # ment referencing the pad name. # # curcop: # COP for statement being deparsed # # curstash: # name of the current package for deparsed code # # subs_todo: # array of [cop_seq, CV, is_format?, name] for subs and formats we still # want to deparse. The fourth element is a pad name thingy for lexical # subs or a string for special blocks. For other subs, it is undef. For # lexical subs, CV may be undef, indicating a stub declaration. # # protos_todo: # as above, but [name, prototype] for subs that never got a GV # # subs_done, forms_done: # keys are addresses of GVs for subs and formats we've already # deparsed (or at least put into subs_todo) # # subs_declared # keys are names of subs for which we've printed declarations. # That means we can omit parentheses from the arguments. It also means we # need to put CORE:: on core functions of the same name. # # in_subst_repl # True when deparsing the replacement part of a substitution. # # in_refgen # True when deparsing the argument to \. # # parens: -p # linenums: -l # unquote: -q # cuddle: ' ' or '\n', depending on -sC # indent_size: -si # use_tabs: -sT # ex_const: -sv # A little explanation of how precedence contexts and associativity # work: # # deparse() calls each per-op subroutine with an argument $cx (short # for context, but not the same as the cx* in the perl core), which is # a number describing the op's parents in terms of precedence, whether # they're inside an expression or at statement level, etc. (see # chart below). When ops with children call deparse on them, they pass # along their precedence. Fractional values are used to implement # associativity ('($x + $y) + $z' => '$x + $y + $y') and related # parentheses hacks. The major disadvantage of this scheme is that # it doesn't know about right sides and left sides, so say if you # assign a listop to a variable, it can't tell it's allowed to leave # the parens off the listop. # Precedences: # 26 [TODO] inside interpolation context ("") # 25 left terms and list operators (leftward) # 24 left -> # 23 nonassoc ++ -- # 22 right ** # 21 right ! ~ \ and unary + and - # 20 left =~ !~ # 19 left * / % x # 18 left + - . # 17 left << >> # 16 nonassoc named unary operators # 15 nonassoc < > <= >= lt gt le ge # 14 nonassoc == != <=> eq ne cmp # 13 left & # 12 left | ^ # 11 left && # 10 left || # 9 nonassoc .. ... # 8 right ?: # 7 right = += -= *= etc. # 6 left , => # 5 nonassoc list operators (rightward) # 4 right not # 3 left and # 2 left or xor # 1 statement modifiers # 0.5 statements, but still print scopes as do { ... } # 0 statement level # -1 format body # Nonprinting characters with special meaning: # \cS - steal parens (see maybe_parens_unop) # \n - newline and indent # \t - increase indent # \b - decrease indent ('outdent') # \f - flush left (no indent) # \cK - kill following semicolon, if any # Semicolon handling: # - Individual statements are not deparsed with trailing semicolons. # (If necessary, \cK is tacked on to the end.) # - Whatever code joins statements together or emits them (lineseq, # scopeop, deparse_root) is responsible for adding semicolons where # necessary. # - use statements are deparsed with trailing semicolons because they are # immediately concatenated with the following statement. # - indent() removes semicolons wherever it sees \cK. BEGIN { for (qw[ const stringify rv2sv list glob pushmark null aelem kvaslice kvhslice padsv nextstate dbstate rv2av rv2hv helem custom ]) { eval "sub OP_\U$_ () { " . opnumber($_) . "}" }} # _pessimise_walk(): recursively walk the optree of a sub, # possibly undoing optimisations along the way. sub _pessimise_walk { my ($self, $startop) = @_; return unless $$startop; my ($op, $prevop); for ($op = $startop; $$op; $prevop = $op, $op = $op->sibling) { my $ppname = $op->name; # pessimisations start here if ($ppname eq "padrange") { # remove PADRANGE: # the original optimisation either (1) changed this: # pushmark -> (various pad and list and null ops) -> the_rest # or (2), for the = @_ case, changed this: # pushmark -> gv[_] -> rv2av -> (pad stuff) -> the_rest # into this: # padrange ----------------------------------------> the_rest # so we just need to convert the padrange back into a # pushmark, and in case (1), set its op_next to op_sibling, # which is the head of the original chain of optimised-away # pad ops, or for (2), set it to sibling->first, which is # the original gv[_]. $B::overlay->{$$op} = { type => OP_PUSHMARK, name => 'pushmark', private => ($op->private & OPpLVAL_INTRO), }; } # pessimisations end here if (class($op) eq 'PMOP') { if (ref($op->pmreplroot) && ${$op->pmreplroot} && $op->pmreplroot->isa( 'B::OP' )) { $self-> _pessimise_walk($op->pmreplroot); } # pessimise any /(?{...})/ code blocks my ($re, $cv); my $code_list = $op->code_list; if ($$code_list) { $self->_pessimise_walk($code_list); } elsif (${$re = $op->pmregexp} && ${$cv = $re->qr_anoncv}) { $code_list = $cv->ROOT # leavesub ->first # qr ->code_list; # list $self->_pessimise_walk($code_list); } } if ($op->flags & OPf_KIDS) { $self-> _pessimise_walk($op->first); } } } # _pessimise_walk_exe(): recursively walk the op_next chain of a sub, # possibly undoing optimisations along the way. sub _pessimise_walk_exe { my ($self, $startop, $visited) = @_; no warnings 'recursion'; return unless $$startop; return if $visited->{$$startop}; my ($op, $prevop); for ($op = $startop; $$op; $prevop = $op, $op = $op->next) { last if $visited->{$$op}; $visited->{$$op} = 1; my $ppname = $op->name; if ($ppname =~ /^((and|d?or)(assign)?|(map|grep)while|range|cond_expr|once)$/ # entertry is also a logop, but its op_other invariably points # into the same chain as the main execution path, so we skip it ) { $self->_pessimise_walk_exe($op->other, $visited); } elsif ($ppname eq "subst") { $self->_pessimise_walk_exe($op->pmreplstart, $visited); } elsif ($ppname =~ /^(enter(loop|iter))$/) { # redoop and nextop will already be covered by the main block # of the loop $self->_pessimise_walk_exe($op->lastop, $visited); } # pessimisations start here } } # Go through an optree and "remove" some optimisations by using an # overlay to selectively modify or un-null some ops. Deparsing in the # absence of those optimisations is then easier. # # Note that older optimisations are not removed, as Deparse was already # written to recognise them before the pessimise/overlay system was added. sub pessimise { my ($self, $root, $start) = @_; no warnings 'recursion'; # walk tree in root-to-branch order $self->_pessimise_walk($root); my %visited; # walk tree in execution order $self->_pessimise_walk_exe($start, \%visited); } sub null { my $op = shift; return class($op) eq "NULL"; } # Add a CV to the list of subs that still need deparsing. sub todo { my $self = shift; my($cv, $is_form, $name) = @_; my $cvfile = $cv->FILE//''; return unless ($cvfile eq $0 || exists $self->{files}{$cvfile}); my $seq; if ($cv->OUTSIDE_SEQ) { $seq = $cv->OUTSIDE_SEQ; } elsif (!null($cv->START) and is_state($cv->START)) { $seq = $cv->START->cop_seq; } else { $seq = 0; } my $stash = $cv->STASH; if (class($stash) eq 'HV') { $self->{packs}{$stash->NAME}++; } push @{$self->{'subs_todo'}}, [$seq, $cv, $is_form, $name]; } # Pop the next sub from the todo list and deparse it sub next_todo { my $self = shift; my $ent = shift @{$self->{'subs_todo'}}; my ($seq, $cv, $is_form, $name) = @$ent; # any 'use strict; package foo' that should come before the sub # declaration to sync with the first COP of the sub my $pragmata = ''; if ($cv and !null($cv->START) and is_state($cv->START)) { $pragmata = $self->pragmata($cv->START); } if (ref $name) { # lexical sub # emit the sub. my @text; my $flags = $name->FLAGS; push @text, !$cv || $seq <= $name->COP_SEQ_RANGE_LOW ? $self->keyword($flags & SVpad_OUR ? "our" : $flags & SVpad_STATE ? "state" : "my") . " " : ""; # XXX We would do $self->keyword("sub"), but ‘my CORE::sub’ # doesn’t work and ‘my sub’ ignores a &sub in scope. I.e., # we have a core bug here. push @text, "sub " . substr $name->PVX, 1; if ($cv) { # my sub foo { } push @text, " " . $self->deparse_sub($cv); $text[-1] =~ s/ ;$/;/; } else { # my sub foo; push @text, ";\n"; } return $pragmata . join "", @text; } my $gv = $cv->GV; $name //= $self->gv_name($gv); if ($is_form) { return $pragmata . $self->keyword("format") . " $name =\n" . $self->deparse_format($cv). "\n"; } else { my $use_dec; if ($name eq "BEGIN") { $use_dec = $self->begin_is_use($cv); if (defined ($use_dec) and $self->{'expand'} < 5) { return $pragmata if 0 == length($use_dec); # XXX bit of a hack: Test::More's use_ok() method # builds a fake use statement which deparses as, e.g. # use Net::Ping (@{$args[0];}); # As well as being superfluous (the use_ok() is deparsed # too) and ugly, it fails under use strict and otherwise # makes use of a lexical var that's not in scope. # So strip it out. return $pragmata if $use_dec =~ m/ \A use \s \S+ \s \(\@\{ ( \s*\#line\ \d+\ \".*"\s* )? \$args\[0\];\}\); \n \Z /x; $use_dec =~ s/^(use|no)\b/$self->keyword($1)/e; } } my $l = ''; if ($self->{'linenums'}) { my $line = $gv->LINE; my $file = $gv->FILE; $l = "\n\f#line $line \"$file\"\n"; } my $p = ''; my $stash; if (class($cv->STASH) ne "SPECIAL") { $stash = $cv->STASH->NAME; if ($stash ne $self->{'curstash'}) { $p = $self->keyword("package") . " $stash;\n"; $name = "$self->{'curstash'}::$name" unless $name =~ /::/; $self->{'curstash'} = $stash; } } if ($use_dec) { return "$pragmata$p$l$use_dec"; } if ( $name !~ /::/ and $self->lex_in_scope("&$name") || $self->lex_in_scope("&$name", 1) ) { $name = "$self->{'curstash'}::$name"; } elsif (defined $stash) { $name =~ s/^\Q$stash\E::(?!\z|.*::)//; } my $ret = "$pragmata${p}${l}" . $self->keyword("sub") . " $name " . $self->deparse_sub($cv); $self->{'subs_declared'}{$name} = 1; return $ret; } } # Return a "use" declaration for this BEGIN block, if appropriate sub begin_is_use { my ($self, $cv) = @_; my $root = $cv->ROOT; local @$self{qw'curcv curcvlex'} = ($cv); local $B::overlay = {}; $self->pessimise($root, $cv->START); #require B::Debug; #B::walkoptree($cv->ROOT, "debug"); my $lineseq = $root->first; return if $lineseq->name ne "lineseq"; my $req_op = $lineseq->first->sibling; return if $req_op->name ne "require"; # maybe it's C rather than C return if ($req_op->first->name ne 'const'); my $module; if ($req_op->first->private & OPpCONST_BARE) { # Actually it should always be a bareword $module = $self->const_sv($req_op->first)->PV; $module =~ s[/][::]g; $module =~ s/.pm$//; } else { $module = $self->const($self->const_sv($req_op->first), 6); } my $version; my $version_op = $req_op->sibling; return if class($version_op) eq "NULL"; if ($version_op->name eq "lineseq") { # We have a version parameter; skip nextstate & pushmark my $constop = $version_op->first->next->next; return unless $self->const_sv($constop)->PV eq $module; $constop = $constop->sibling; $version = $self->const_sv($constop); if (class($version) eq "IV") { $version = $version->int_value; } elsif (class($version) eq "NV") { $version = $version->NV; } elsif (class($version) ne "PVMG") { # Includes PVIV and PVNV $version = $version->PV; } else { # version specified as a v-string $version = 'v'.join '.', map ord, split //, $version->PV; } $constop = $constop->sibling; return if $constop->name ne "method_named"; return if $self->meth_sv($constop)->PV ne "VERSION"; } $lineseq = $version_op->sibling; return if $lineseq->name ne "lineseq"; my $entersub = $lineseq->first->sibling; if ($entersub->name eq "stub") { return "use $module $version ();\n" if defined $version; return "use $module ();\n"; } return if $entersub->name ne "entersub"; # See if there are import arguments my $args = ''; my $svop = $entersub->first->sibling; # Skip over pushmark return unless $self->const_sv($svop)->PV eq $module; # Pull out the arguments for ($svop=$svop->sibling; index($svop->name, "method_") != 0; $svop = $svop->sibling) { $args .= ", " if length($args); $args .= $self->deparse($svop, 6); } my $use = 'use'; my $method_named = $svop; return if $method_named->name ne "method_named"; my $method_name = $self->meth_sv($method_named)->PV; if ($method_name eq "unimport") { $use = 'no'; } # Certain pragmas are dealt with using hint bits, # so we ignore them here if ($module eq 'strict' || $module eq 'integer' || $module eq 'bytes' || $module eq 'warnings' || $module eq 'feature') { return ""; } if (defined $version && length $args) { return "$use $module $version ($args);\n"; } elsif (defined $version) { return "$use $module $version;\n"; } elsif (length $args) { return "$use $module ($args);\n"; } else { return "$use $module;\n"; } } sub stash_subs { my ($self, $pack, $seen) = @_; my (@ret, $stash); if (!defined $pack) { $pack = ''; $stash = \%::; } else { $pack =~ s/(::)?$/::/; no strict 'refs'; $stash = \%{"main::$pack"}; } return if ($seen ||= {})->{ $INC{"overload.pm"} ? overload::StrVal($stash) : $stash }++; my $stashobj = svref_2object($stash); my %stash = $stashobj->ARRAY; while (my ($key, $val) = each %stash) { my $flags = $val->FLAGS; if ($flags & SVf_ROK) { # A reference. Dump this if it is a reference to a CV. If it # is a constant acting as a proxy for a full subroutine, then # we may or may not have to dump it. If some form of perl- # space visible code must have created it, be it a use # statement, or some direct symbol-table manipulation code that # we will deparse, then we don’t want to dump it. If it is the # result of a declaration like sub f () { 42 } then we *do* # want to dump it. The only way to distinguish these seems # to be the SVs_PADTMP flag on the constant, which is admit- # tedly a hack. my $class = class(my $referent = $val->RV); if ($class eq "CV") { $self->todo($referent, 0); } elsif ( $class !~ /^(AV|HV|CV|FM|IO|SPECIAL)\z/ # A more robust way to write that would be this, but B does # not provide the SVt_ constants: # ($referent->FLAGS & B::SVTYPEMASK) < B::SVt_PVAV and $referent->FLAGS & SVs_PADTMP ) { push @{$self->{'protos_todo'}}, [$pack . $key, $val]; } } elsif ($flags & (SVf_POK|SVf_IOK)) { # Just a prototype. As an ugly but fairly effective way # to find out if it belongs here is to see if the AUTOLOAD # (if any) for the stash was defined in one of our files. my $A = $stash{"AUTOLOAD"}; if (defined ($A) && class($A) eq "GV" && defined($A->CV) && class($A->CV) eq "CV") { my $AF = $A->FILE; next unless $AF eq $0 || exists $self->{'files'}{$AF}; } push @{$self->{'protos_todo'}}, [$pack . $key, $flags & SVf_POK ? $val->PV: undef]; } elsif (class($val) eq "GV") { if (class(my $cv = $val->CV) ne "SPECIAL") { next if $self->{'subs_done'}{$$val}++; # Ignore imposters (aliases etc) my $name = $cv->NAME_HEK; if(defined $name) { # avoid using $cv->GV here because if the $val GV is # an alias, CvGV() could upgrade the real stash entry # from an RV to a GV next unless $name eq $key; next unless $$stashobj == ${$cv->STASH}; } else { next if $$val != ${$cv->GV}; } $self->todo($cv, 0); } if (class(my $cv = $val->FORM) ne "SPECIAL") { next if $self->{'forms_done'}{$$val}++; next if $$val != ${$cv->GV}; # Ignore imposters $self->todo($cv, 1); } if (class($val->HV) ne "SPECIAL" && $key =~ /::$/) { $self->stash_subs($pack . $key, $seen); } } } } sub print_protos { my $self = shift; my $ar; my @ret; foreach $ar (@{$self->{'protos_todo'}}) { if (ref $ar->[1]) { # Only print a constant if it occurs in the same package as a # dumped sub. This is not perfect, but a heuristic that will # hopefully work most of the time. Ideally we would use # CvFILE, but a constant stub has no CvFILE. my $pack = ($ar->[0] =~ /(.*)::/)[0]; next if $pack and !$self->{packs}{$pack} } my $body = defined $ar->[1] ? ref $ar->[1] ? " () {\n " . $self->const($ar->[1]->RV,0) . ";\n}" : " (". $ar->[1] . ");" : ";"; push @ret, "sub " . $ar->[0] . "$body\n"; } delete $self->{'protos_todo'}; return @ret; } sub style_opts { my $self = shift; my $opts = shift; my $opt; while (length($opt = substr($opts, 0, 1))) { if ($opt eq "C") { $self->{'cuddle'} = " "; $opts = substr($opts, 1); } elsif ($opt eq "i") { $opts =~ s/^i(\d+)//; $self->{'indent_size'} = $1; } elsif ($opt eq "T") { $self->{'use_tabs'} = 1; $opts = substr($opts, 1); } elsif ($opt eq "v") { $opts =~ s/^v([^.]*)(.|$)//; $self->{'ex_const'} = $1; } } } sub new { my $class = shift; my $self = bless {}, $class; $self->{'cuddle'} = "\n"; $self->{'curcop'} = undef; $self->{'curstash'} = "main"; $self->{'ex_const'} = "'???'"; $self->{'expand'} = 0; $self->{'files'} = {}; $self->{'packs'} = {}; $self->{'indent_size'} = 4; $self->{'linenums'} = 0; $self->{'parens'} = 0; $self->{'subs_todo'} = []; $self->{'unquote'} = 0; $self->{'use_dumper'} = 0; $self->{'use_tabs'} = 0; $self->{'ambient_arybase'} = 0; $self->{'ambient_warnings'} = undef; # Assume no lexical warnings $self->{'ambient_hints'} = 0; $self->{'ambient_hinthash'} = undef; $self->init(); while (my $arg = shift @_) { if ($arg eq "-d") { $self->{'use_dumper'} = 1; require Data::Dumper; } elsif ($arg =~ /^-f(.*)/) { $self->{'files'}{$1} = 1; } elsif ($arg eq "-l") { $self->{'linenums'} = 1; } elsif ($arg eq "-p") { $self->{'parens'} = 1; } elsif ($arg eq "-P") { $self->{'noproto'} = 1; } elsif ($arg eq "-q") { $self->{'unquote'} = 1; } elsif (substr($arg, 0, 2) eq "-s") { $self->style_opts(substr $arg, 2); } elsif ($arg =~ /^-x(\d)$/) { $self->{'expand'} = $1; } } return $self; } { # Mask out the bits that L uses my $WARN_MASK; BEGIN { $WARN_MASK = $warnings::Bits{all} | $warnings::DeadBits{all}; } sub WARN_MASK () { return $WARN_MASK; } } # Initialise the contextual information, either from # defaults provided with the ambient_pragmas method, # or from perl's own defaults otherwise. sub init { my $self = shift; $self->{'arybase'} = $self->{'ambient_arybase'}; $self->{'warnings'} = defined ($self->{'ambient_warnings'}) ? $self->{'ambient_warnings'} & WARN_MASK : undef; $self->{'hints'} = $self->{'ambient_hints'}; $self->{'hinthash'} = $self->{'ambient_hinthash'}; # also a convenient place to clear out subs_declared delete $self->{'subs_declared'}; } sub compile { my(@args) = @_; return sub { my $self = B::Deparse->new(@args); # First deparse command-line args if (defined $^I) { # deparse -i print q(BEGIN { $^I = ).perlstring($^I).qq(; }\n); } if ($^W) { # deparse -w print qq(BEGIN { \$^W = $^W; }\n); } if ($/ ne "\n" or defined $O::savebackslash) { # deparse -l and -0 my $fs = perlstring($/) || 'undef'; my $bs = perlstring($O::savebackslash) || 'undef'; print qq(BEGIN { \$/ = $fs; \$\\ = $bs; }\n); } my @BEGINs = B::begin_av->isa("B::AV") ? B::begin_av->ARRAY : (); my @UNITCHECKs = B::unitcheck_av->isa("B::AV") ? B::unitcheck_av->ARRAY : (); my @CHECKs = B::check_av->isa("B::AV") ? B::check_av->ARRAY : (); my @INITs = B::init_av->isa("B::AV") ? B::init_av->ARRAY : (); my @ENDs = B::end_av->isa("B::AV") ? B::end_av->ARRAY : (); my @names = qw(BEGIN UNITCHECK CHECK INIT END); my @blocks = \(@BEGINs, @UNITCHECKs, @CHECKs, @INITs, @ENDs); while (@names) { my ($name, $blocks) = (shift @names, shift @blocks); for my $block (@$blocks) { $self->todo($block, 0, $name); } } $self->stash_subs(); local($SIG{"__DIE__"}) = sub { if ($self->{'curcop'}) { my $cop = $self->{'curcop'}; my($line, $file) = ($cop->line, $cop->file); print STDERR "While deparsing $file near line $line,\n"; } }; $self->{'curcv'} = main_cv; $self->{'curcvlex'} = undef; print $self->print_protos; @{$self->{'subs_todo'}} = sort {$a->[0] <=> $b->[0]} @{$self->{'subs_todo'}}; my $root = main_root; local $B::overlay = {}; unless (null $root) { $self->pad_subs($self->{'curcv'}); # Check for a stub-followed-by-ex-cop, resulting from a program # consisting solely of sub declarations. For backward-compati- # bility (and sane output) we don’t want to emit the stub. # leave # enter # stub # ex-nextstate (or ex-dbstate) my $kid; if ( $root->name eq 'leave' and ($kid = $root->first)->name eq 'enter' and !null($kid = $kid->sibling) and $kid->name eq 'stub' and !null($kid = $kid->sibling) and $kid->name eq 'null' and class($kid) eq 'COP' and null $kid->sibling ) { # ignore } else { $self->pessimise($root, main_start); print $self->indent($self->deparse_root($root)), "\n"; } } my @text; while (scalar(@{$self->{'subs_todo'}})) { push @text, $self->next_todo; } print $self->indent(join("", @text)), "\n" if @text; # Print __DATA__ section, if necessary no strict 'refs'; my $laststash = defined $self->{'curcop'} ? $self->{'curcop'}->stash->NAME : $self->{'curstash'}; if (defined *{$laststash."::DATA"}{IO}) { print $self->keyword("package") . " $laststash;\n" unless $laststash eq $self->{'curstash'}; print $self->keyword("__DATA__") . "\n"; print readline(*{$laststash."::DATA"}); } } } sub coderef2text { my $self = shift; my $sub = shift; croak "Usage: ->coderef2text(CODEREF)" unless UNIVERSAL::isa($sub, "CODE"); $self->init(); local $self->{in_coderef2text} = 1; return $self->indent($self->deparse_sub(svref_2object($sub))); } my %strict_bits = do { local $^H; map +($_ => strict::bits($_)), qw/refs subs vars/ }; sub ambient_pragmas { my $self = shift; my ($arybase, $hint_bits, $warning_bits, $hinthash) = (0, 0); while (@_ > 1) { my $name = shift(); my $val = shift(); if ($name eq 'strict') { require strict; if ($val eq 'none') { $hint_bits &= $strict_bits{$_} for qw/refs subs vars/; next(); } my @names; if ($val eq "all") { @names = qw/refs subs vars/; } elsif (ref $val) { @names = @$val; } else { @names = split' ', $val; } $hint_bits |= $strict_bits{$_} for @names; } elsif ($name eq '$[') { if (OPpCONST_ARYBASE) { $arybase = $val; } else { croak "\$[ can't be non-zero on this perl" unless $val == 0; } } elsif ($name eq 'integer' || $name eq 'bytes' || $name eq 'utf8') { require "$name.pm"; if ($val) { $hint_bits |= ${$::{"${name}::"}{"hint_bits"}}; } else { $hint_bits &= ~${$::{"${name}::"}{"hint_bits"}}; } } elsif ($name eq 're') { require re; if ($val eq 'none') { $hint_bits &= ~re::bits(qw/taint eval/); next(); } my @names; if ($val eq 'all') { @names = qw/taint eval/; } elsif (ref $val) { @names = @$val; } else { @names = split' ',$val; } $hint_bits |= re::bits(@names); } elsif ($name eq 'warnings') { if ($val eq 'none') { $warning_bits = $warnings::NONE; next(); } my @names; if (ref $val) { @names = @$val; } else { @names = split/\s+/, $val; } $warning_bits = $warnings::NONE if !defined ($warning_bits); $warning_bits |= warnings::bits(@names); } elsif ($name eq 'warning_bits') { $warning_bits = $val; } elsif ($name eq 'hint_bits') { $hint_bits = $val; } elsif ($name eq '%^H') { $hinthash = $val; } else { croak "Unknown pragma type: $name"; } } if (@_) { croak "The ambient_pragmas method expects an even number of args"; } $self->{'ambient_arybase'} = $arybase; $self->{'ambient_warnings'} = $warning_bits; $self->{'ambient_hints'} = $hint_bits; $self->{'ambient_hinthash'} = $hinthash; } # This method is the inner loop, so try to keep it simple sub deparse { my $self = shift; my($op, $cx) = @_; Carp::confess("Null op in deparse") if !defined($op) || class($op) eq "NULL"; my $meth = "pp_" . $op->name; return $self->$meth($op, $cx); } sub indent { my $self = shift; my $txt = shift; # \cK also swallows a preceding line break when followed by a # semicolon. $txt =~ s/\n\cK;//g; my @lines = split(/\n/, $txt); my $leader = ""; my $level = 0; my $line; for $line (@lines) { my $cmd = substr($line, 0, 1); if ($cmd eq "\t" or $cmd eq "\b") { $level += ($cmd eq "\t" ? 1 : -1) * $self->{'indent_size'}; if ($self->{'use_tabs'}) { $leader = "\t" x ($level / 8) . " " x ($level % 8); } else { $leader = " " x $level; } $line = substr($line, 1); } if (index($line, "\f") > 0) { $line =~ s/\f/\n/; } if (substr($line, 0, 1) eq "\f") { $line = substr($line, 1); # no indent } else { $line = $leader . $line; } $line =~ s/\cK;?//g; } return join("\n", @lines); } sub pad_subs { my ($self, $cv) = @_; my $padlist = $cv->PADLIST; my @names = $padlist->ARRAYelt(0)->ARRAY; my @values = $padlist->ARRAYelt(1)->ARRAY; my @todo; PADENTRY: for my $ix (0.. $#names) { for $_ ($names[$ix]) { next if class($_) eq "SPECIAL"; my $name = $_->PVX; if (defined $name && $name =~ /^&./) { my $low = $_->COP_SEQ_RANGE_LOW; my $flags = $_->FLAGS; my $outer = $flags & PADNAMEt_OUTER; if ($flags & SVpad_OUR) { push @todo, [$low, undef, 0, $_] # [seq, no cv, not format, padname] unless $outer; next; } my $protocv = $flags & SVpad_STATE ? $values[$ix] : $_->PROTOCV; if (class ($protocv) ne 'CV') { my $flags = $flags; my $cv = $cv; my $name = $_; while ($flags & PADNAMEt_OUTER && class ($protocv) ne 'CV') { $cv = $cv->OUTSIDE; next PADENTRY if class($cv) eq 'SPECIAL'; # XXX freed? my $padlist = $cv->PADLIST; my $ix = $name->PARENT_PAD_INDEX; $name = $padlist->NAMES->ARRAYelt($ix); $flags = $name->FLAGS; $protocv = $flags & SVpad_STATE ? $padlist->ARRAYelt(1)->ARRAYelt($ix) : $name->PROTOCV; } } my $defined_in_this_sub = ${$protocv->OUTSIDE} == $$cv || do { my $other = $protocv->PADLIST; $$other && $other->outid == $padlist->id; }; if ($flags & PADNAMEt_OUTER) { next unless $defined_in_this_sub; push @todo, [$protocv->OUTSIDE_SEQ, $protocv, 0, $_]; next; } my $outseq = $protocv->OUTSIDE_SEQ; if ($outseq <= $low) { # defined before its name is visible, so it’s gotta be # declared and defined at once: my sub foo { ... } push @todo, [$low, $protocv, 0, $_]; } else { # declared and defined separately: my sub f; sub f { ... } push @todo, [$low, undef, 0, $_]; push @todo, [$outseq, $protocv, 0, $_] if $defined_in_this_sub; } } }} @{$self->{'subs_todo'}} = sort {$a->[0] <=> $b->[0]} @{$self->{'subs_todo'}}, @todo } # deparse_argops(): deparse, if possible, a sequence of argcheck + argelem # ops into a subroutine signature. If successful, return the first op # following the signature ops plus the signature string; else return the # empty list. # # Normally a bunch of argelem ops will have been generated by the # signature parsing, but it's possible that ops have been added manually # or altered. In this case we return "()" and fall back to general # deparsing of the individual sigelems as 'my $x = $_[N]' etc. # # We're only called if the first two ops are nextstate and argcheck. sub deparse_argops { my ($self, $firstop, $cv) = @_; my @sig; my $o = $firstop; return if $o->label; #first nextstate; # OP_ARGCHECK $o = $o->sibling; my ($params, $opt_params, $slurpy) = $o->aux_list($cv); my $mandatory = $params - $opt_params; my $seen_slurpy = 0; my $last_ix = -1; # keep looking for valid nextstate + argelem pairs while (1) { # OP_NEXTSTATE $o = $o->sibling; last unless $$o; last unless $o->name =~ /^(next|db)state$/; last if $o->label; # OP_ARGELEM my $o2 = $o->sibling; last unless $$o2; if ($o2->name eq 'argelem') { my $ix = $o2->string($cv); while (++$last_ix < $ix) { push @sig, $last_ix < $mandatory ? '$' : '$='; } my $var = $self->padname($o2->targ); if ($var =~ /^[@%]/) { return if $seen_slurpy; $seen_slurpy = 1; return if $ix != $params or !$slurpy or substr($var,0,1) ne $slurpy; } else { return if $ix >= $params; } if ($o2->flags & OPf_KIDS) { my $kid = $o2->first; return unless $$kid and $kid->name eq 'argdefelem'; my $def = $self->deparse($kid->first, 7); $def = "($def)" if $kid->first->flags & OPf_PARENS; $var .= " = $def"; } push @sig, $var; } elsif ($o2->name eq 'null' and ($o2->flags & OPf_KIDS) and $o2->first->name eq 'argdefelem') { # special case - a void context default expression: $ = expr my $defop = $o2->first; my $ix = $defop->targ; while (++$last_ix < $ix) { push @sig, $last_ix < $mandatory ? '$' : '$='; } return if $last_ix >= $params or $last_ix < $mandatory; my $def = $self->deparse($defop->first, 7); $def = "($def)" if $defop->first->flags & OPf_PARENS; push @sig, '$ = ' . $def; } else { last; } $o = $o2; } while (++$last_ix < $params) { push @sig, $last_ix < $mandatory ? '$' : '$='; } push @sig, $slurpy if $slurpy and !$seen_slurpy; return ($o, join(', ', @sig)); } # Deparse a sub. Returns everything except the 'sub foo', # e.g. ($$) : method { ...; } # or : prototype($$) lvalue ($a, $b) { ...; }; sub deparse_sub { my $self = shift; my $cv = shift; my @attrs; my $proto; my $sig; Carp::confess("NULL in deparse_sub") if !defined($cv) || $cv->isa("B::NULL"); Carp::confess("SPECIAL in deparse_sub") if $cv->isa("B::SPECIAL"); local $self->{'curcop'} = $self->{'curcop'}; my $has_sig = $self->{hinthash}{feature_signatures}; if ($cv->FLAGS & SVf_POK) { my $myproto = $cv->PV; if ($has_sig) { push @attrs, "prototype($myproto)"; } else { $proto = $myproto; } } if ($cv->CvFLAGS & (CVf_METHOD|CVf_LOCKED|CVf_LVALUE|CVf_ANONCONST)) { push @attrs, "lvalue" if $cv->CvFLAGS & CVf_LVALUE; push @attrs, "method" if $cv->CvFLAGS & CVf_METHOD; push @attrs, "const" if $cv->CvFLAGS & CVf_ANONCONST; } local($self->{'curcv'}) = $cv; local($self->{'curcvlex'}); local(@$self{qw'curstash warnings hints hinthash'}) = @$self{qw'curstash warnings hints hinthash'}; my $body; my $root = $cv->ROOT; local $B::overlay = {}; if (not null $root) { $self->pad_subs($cv); $self->pessimise($root, $cv->START); my $lineseq = $root->first; if ($lineseq->name eq "lineseq") { my $firstop = $lineseq->first; if ($has_sig) { my $o2; # try to deparse first few ops as a signature if possible if ( $$firstop and $firstop->name =~ /^(next|db)state$/ and (($o2 = $firstop->sibling)) and $$o2) { if ($o2->name eq 'argcheck') { my ($nexto, $mysig) = $self->deparse_argops($firstop, $cv); if (defined $nexto) { $firstop = $nexto; $sig = $mysig; } } } } my @ops; for (my $o = $firstop; $$o; $o=$o->sibling) { push @ops, $o; } $body = $self->lineseq(undef, 0, @ops).";"; if (!$has_sig and $ops[-1]->name =~ /^(next|db)state$/) { # this handles void context in # use feature signatures; sub ($=1) {} $body .= "\n()"; } my $scope_en = $self->find_scope_en($lineseq); if (defined $scope_en) { my $subs = join"", $self->seq_subs($scope_en); $body .= ";\n$subs" if length($subs); } } else { $body = $self->deparse($root->first, 0); } my $l = ''; if ($self->{'linenums'}) { # a glob's gp_line is set from the line containing a # sub's closing '}' if the CV is the first use of the GV. # So make sure the linenum is set correctly for '}' my $gv = $cv->GV; my $line = $gv->LINE; my $file = $gv->FILE; $l = "\f#line $line \"$file\"\n"; } $body = "{\n\t$body\n$l\b}"; } else { my $sv = $cv->const_sv; if ($$sv) { # uh-oh. inlinable sub... format it differently $body = "{ " . $self->const($sv, 0) . " }\n"; } else { # XSUB? (or just a declaration) $body = ';' } } $proto = defined $proto ? "($proto) " : ""; $sig = defined $sig ? "($sig) " : ""; my $attrs = ''; $attrs = ': ' . join('', map "$_ ", @attrs) if @attrs; return "$proto$attrs$sig$body\n"; } sub deparse_format { my $self = shift; my $form = shift; my @text; local($self->{'curcv'}) = $form; local($self->{'curcvlex'}); local($self->{'in_format'}) = 1; local(@$self{qw'curstash warnings hints hinthash'}) = @$self{qw'curstash warnings hints hinthash'}; my $op = $form->ROOT; local $B::overlay = {}; $self->pessimise($op, $form->START); my $kid; return "\f." if $op->first->name eq 'stub' || $op->first->name eq 'nextstate'; $op = $op->first->first; # skip leavewrite, lineseq while (not null $op) { $op = $op->sibling; # skip nextstate my @exprs; $kid = $op->first->sibling; # skip pushmark push @text, "\f".$self->const_sv($kid)->PV; $kid = $kid->sibling; for (; not null $kid; $kid = $kid->sibling) { push @exprs, $self->deparse($kid, -1); $exprs[-1] =~ s/;\z//; } push @text, "\f".join(", ", @exprs)."\n" if @exprs; $op = $op->sibling; } return join("", @text) . "\f."; } sub is_scope { my $op = shift; return $op->name eq "leave" || $op->name eq "scope" || $op->name eq "lineseq" || ($op->name eq "null" && class($op) eq "UNOP" && (is_scope($op->first) || $op->first->name eq "enter")); } sub is_state { my $name = $_[0]->name; return $name eq "nextstate" || $name eq "dbstate" || $name eq "setstate"; } sub is_miniwhile { # check for one-line loop ('foo() while $y--') my $op = shift; return (!null($op) and null($op->sibling) and $op->name eq "null" and class($op) eq "UNOP" and (($op->first->name =~ /^(and|or)$/ and $op->first->first->sibling->name eq "lineseq") or ($op->first->name eq "lineseq" and not null $op->first->first->sibling and $op->first->first->sibling->name eq "unstack") )); } # Check if the op and its sibling are the initialization and the rest of a # for (..;..;..) { ... } loop sub is_for_loop { my $op = shift; # This OP might be almost anything, though it won't be a # nextstate. (It's the initialization, so in the canonical case it # will be an sassign.) The sibling is (old style) a lineseq whose # first child is a nextstate and whose second is a leaveloop, or # (new style) an unstack whose sibling is a leaveloop. my $lseq = $op->sibling; return 0 unless !is_state($op) and !null($lseq); if ($lseq->name eq "lineseq") { if ($lseq->first && !null($lseq->first) && is_state($lseq->first) && (my $sib = $lseq->first->sibling)) { return (!null($sib) && $sib->name eq "leaveloop"); } } elsif ($lseq->name eq "unstack" && ($lseq->flags & OPf_SPECIAL)) { my $sib = $lseq->sibling; return $sib && !null($sib) && $sib->name eq "leaveloop"; } return 0; } sub is_scalar { my $op = shift; return ($op->name eq "rv2sv" or $op->name eq "padsv" or $op->name eq "gv" or # only in array/hash constructs $op->flags & OPf_KIDS && !null($op->first) && $op->first->name eq "gvsv"); } sub maybe_parens { my $self = shift; my($text, $cx, $prec) = @_; if ($prec < $cx # unary ops nest just fine or $prec == $cx and $cx != 4 and $cx != 16 and $cx != 21 or $self->{'parens'}) { $text = "($text)"; # In a unop, let parent reuse our parens; see maybe_parens_unop $text = "\cS" . $text if $cx == 16; return $text; } else { return $text; } } # same as above, but get around the 'if it looks like a function' rule sub maybe_parens_unop { my $self = shift; my($name, $kid, $cx) = @_; if ($cx > 16 or $self->{'parens'}) { $kid = $self->deparse($kid, 1); if ($name eq "umask" && $kid =~ /^\d+$/) { $kid = sprintf("%#o", $kid); } return $self->keyword($name) . "($kid)"; } else { $kid = $self->deparse($kid, 16); if ($name eq "umask" && $kid =~ /^\d+$/) { $kid = sprintf("%#o", $kid); } $name = $self->keyword($name); if (substr($kid, 0, 1) eq "\cS") { # use kid's parens return $name . substr($kid, 1); } elsif (substr($kid, 0, 1) eq "(") { # avoid looks-like-a-function trap with extra parens # ('+' can lead to ambiguities) return "$name(" . $kid . ")"; } else { return "$name $kid"; } } } sub maybe_parens_func { my $self = shift; my($func, $text, $cx, $prec) = @_; if ($prec <= $cx or substr($text, 0, 1) eq "(" or $self->{'parens'}) { return "$func($text)"; } else { return "$func $text"; } } sub find_our_type { my ($self, $name) = @_; $self->populate_curcvlex() if !defined $self->{'curcvlex'}; my $seq = $self->{'curcop'} ? $self->{'curcop'}->cop_seq : 0; for my $a (@{$self->{'curcvlex'}{"o$name"}}) { my ($st, undef, $padname) = @$a; if ($st >= $seq && $padname->FLAGS & SVpad_TYPED) { return $padname->SvSTASH->NAME; } } return ''; } sub maybe_local { my $self = shift; my($op, $cx, $text) = @_; my $name = $op->name; my $our_intro = ($name =~ /^(?:(?:gv|rv2)[ash]v|split|refassign |lv(?:av)?ref)$/x) ? OPpOUR_INTRO : 0; my $lval_intro = $name eq 'split' ? 0 : OPpLVAL_INTRO; # The @a in \(@a) isn't in ref context, but only when the # parens are there. my $need_parens = $self->{'in_refgen'} && $name =~ /[ah]v\z/ && ($op->flags & (OPf_PARENS|OPf_REF)) == OPf_PARENS; if ((my $priv = $op->private) & ($lval_intro|$our_intro)) { my @our_local; push @our_local, "local" if $priv & $lval_intro; push @our_local, "our" if $priv & $our_intro; my $our_local = join " ", map $self->keyword($_), @our_local; if( $our_local[-1] eq 'our' ) { if ( $text !~ /^\W(\w+::)*\w+\z/ and !utf8::decode($text) || $text !~ /^\W(\w+::)*\w+\z/ ) { die "Unexpected our($text)\n"; } $text =~ s/(\w+::)+//; if (my $type = $self->find_our_type($text)) { $our_local .= ' ' . $type; } } return $need_parens ? "($text)" : $text if $self->{'avoid_local'}{$$op}; if ($need_parens) { return "$our_local($text)"; } elsif (want_scalar($op) || $our_local eq 'our') { return "$our_local $text"; } else { return $self->maybe_parens_func("$our_local", $text, $cx, 16); } } else { return $need_parens ? "($text)" : $text; } } sub maybe_targmy { my $self = shift; my($op, $cx, $func, @args) = @_; if ($op->private & OPpTARGET_MY) { my $var = $self->padname($op->targ); my $val = $func->($self, $op, 7, @args); return $self->maybe_parens("$var = $val", $cx, 7); } else { return $func->($self, $op, $cx, @args); } } sub padname_sv { my $self = shift; my $targ = shift; return $self->{'curcv'}->PADLIST->ARRAYelt(0)->ARRAYelt($targ); } sub maybe_my { my $self = shift; my($op, $cx, $text, $padname, $forbid_parens) = @_; # The @a in \(@a) isn't in ref context, but only when the # parens are there. my $need_parens = !$forbid_parens && $self->{'in_refgen'} && $op->name =~ /[ah]v\z/ && ($op->flags & (OPf_PARENS|OPf_REF)) == OPf_PARENS; # The @a in \my @a must not have parens. if (!$need_parens && $self->{'in_refgen'}) { $forbid_parens = 1; } if ($op->private & OPpLVAL_INTRO and not $self->{'avoid_local'}{$$op}) { # Check $padname->FLAGS for statehood, rather than $op->private, # because enteriter ops do not carry the flag. my $my = $self->keyword($padname->FLAGS & SVpad_STATE ? "state" : "my"); if ($padname->FLAGS & SVpad_TYPED) { $my .= ' ' . $padname->SvSTASH->NAME; } if ($need_parens) { return "$my($text)"; } elsif ($forbid_parens || want_scalar($op)) { return "$my $text"; } else { return $self->maybe_parens_func($my, $text, $cx, 16); } } else { return $need_parens ? "($text)" : $text; } } # The following OPs don't have functions: # pp_padany -- does not exist after parsing sub AUTOLOAD { if ($AUTOLOAD =~ s/^.*::pp_//) { warn "unexpected OP_". ($_[1]->type == OP_CUSTOM ? "CUSTOM ($AUTOLOAD)" : uc $AUTOLOAD); return "XXX"; } else { die "Undefined subroutine $AUTOLOAD called"; } } sub DESTROY {} # Do not AUTOLOAD # $root should be the op which represents the root of whatever # we're sequencing here. If it's undefined, then we don't append # any subroutine declarations to the deparsed ops, otherwise we # append appropriate declarations. sub lineseq { my($self, $root, $cx, @ops) = @_; my($expr, @exprs); my $out_cop = $self->{'curcop'}; my $out_seq = defined($out_cop) ? $out_cop->cop_seq : undef; my $limit_seq; if (defined $root) { $limit_seq = $out_seq; my $nseq; $nseq = $self->find_scope_st($root->sibling) if ${$root->sibling}; $limit_seq = $nseq if !defined($limit_seq) or defined($nseq) && $nseq < $limit_seq; } $limit_seq = $self->{'limit_seq'} if defined($self->{'limit_seq'}) && (!defined($limit_seq) || $self->{'limit_seq'} < $limit_seq); local $self->{'limit_seq'} = $limit_seq; $self->walk_lineseq($root, \@ops, sub { push @exprs, $_[0]} ); my $sep = $cx ? '; ' : ";\n"; my $body = join($sep, grep {length} @exprs); my $subs = ""; if (defined $root && defined $limit_seq && !$self->{'in_format'}) { $subs = join "\n", $self->seq_subs($limit_seq); } return join($sep, grep {length} $body, $subs); } sub scopeop { my($real_block, $self, $op, $cx) = @_; my $kid; my @kids; local(@$self{qw'curstash warnings hints hinthash'}) = @$self{qw'curstash warnings hints hinthash'} if $real_block; if ($real_block) { $kid = $op->first->sibling; # skip enter if (is_miniwhile($kid)) { my $top = $kid->first; my $name = $top->name; if ($name eq "and") { $name = $self->keyword("while"); } elsif ($name eq "or") { $name = $self->keyword("until"); } else { # no conditional -> while 1 or until 0 return $self->deparse($top->first, 1) . " " . $self->keyword("while") . " 1"; } my $cond = $top->first; my $body = $cond->sibling->first; # skip lineseq $cond = $self->deparse($cond, 1); $body = $self->deparse($body, 1); return "$body $name $cond"; } } else { $kid = $op->first; } for (; !null($kid); $kid = $kid->sibling) { push @kids, $kid; } if ($cx > 0) { # inside an expression, (a do {} while for lineseq) my $body = $self->lineseq($op, 0, @kids); return is_lexical_subs(@kids) ? $body : ($self->lex_in_scope("&do") ? "CORE::do" : "do") . " {\n\t$body\n\b}"; } else { my $lineseq = $self->lineseq($op, $cx, @kids); return (length ($lineseq) ? "$lineseq;" : ""); } } sub pp_scope { scopeop(0, @_); } sub pp_lineseq { scopeop(0, @_); } sub pp_leave { scopeop(1, @_); } # This is a special case of scopeop and lineseq, for the case of the # main_root. The difference is that we print the output statements as # soon as we get them, for the sake of impatient users. sub deparse_root { my $self = shift; my($op) = @_; local(@$self{qw'curstash warnings hints hinthash'}) = @$self{qw'curstash warnings hints hinthash'}; my @kids; return if null $op->first; # Can happen, e.g., for Bytecode without -k for (my $kid = $op->first->sibling; !null($kid); $kid = $kid->sibling) { push @kids, $kid; } $self->walk_lineseq($op, \@kids, sub { return unless length $_[0]; print $self->indent($_[0].';'); print "\n" unless $_[1] == $#kids; }); } sub walk_lineseq { my ($self, $op, $kids, $callback) = @_; my @kids = @$kids; for (my $i = 0; $i < @kids; $i++) { my $expr = ""; if (is_state $kids[$i]) { $expr = $self->deparse($kids[$i++], 0); if ($i > $#kids) { $callback->($expr, $i); last; } } if (is_for_loop($kids[$i])) { $callback->($expr . $self->for_loop($kids[$i], 0), $i += $kids[$i]->sibling->name eq "unstack" ? 2 : 1); next; } my $expr2 = $self->deparse($kids[$i], (@kids != 1)/2); $expr2 =~ s/^sub :(?!:)/+sub :/; # statement label otherwise $expr .= $expr2; $callback->($expr, $i); } } # The BEGIN {} is used here because otherwise this code isn't executed # when you run B::Deparse on itself. my %globalnames; BEGIN { map($globalnames{$_}++, "SIG", "STDIN", "STDOUT", "STDERR", "INC", "ENV", "ARGV", "ARGVOUT", "_"); } sub gv_name { my $self = shift; my $gv = shift; my $raw = shift; #Carp::confess() unless ref($gv) eq "B::GV"; my $cv = $gv->FLAGS & SVf_ROK ? $gv->RV : 0; my $stash = ($cv || $gv)->STASH->NAME; my $name = $raw ? $cv ? $cv->NAME_HEK || $cv->GV->NAME : $gv->NAME : $cv ? B::safename($cv->NAME_HEK || $cv->GV->NAME) : $gv->SAFENAME; if ($stash eq 'main' && $name =~ /^::/) { $stash = '::'; } elsif (($stash eq 'main' && ($globalnames{$name} || $name =~ /^[^A-Za-z_:]/)) or ($stash eq $self->{'curstash'} && !$globalnames{$name} && ($stash eq 'main' || $name !~ /::/)) ) { $stash = ""; } else { $stash = $stash . "::"; } if (!$raw and $name =~ /^(\^..|{)/) { $name = "{$name}"; # ${^WARNING_BITS}, etc and ${ } return $stash . $name; } # Return the name to use for a stash variable. # If a lexical with the same name is in scope, or # if strictures are enabled, it may need to be # fully-qualified. sub stash_variable { my ($self, $prefix, $name, $cx) = @_; return $prefix.$self->maybe_qualify($prefix, $name) if $name =~ /::/; unless ($prefix eq '$' || $prefix eq '@' || $prefix eq '&' || #' $prefix eq '%' || $prefix eq '$#') { return "$prefix$name"; } if ($name =~ /^[^[:alpha:]_+-]$/) { if (defined $cx && $cx == 26) { if ($prefix eq '@') { return "$prefix\{$name}"; } elsif ($name eq '#') { return '${#}' } # "${#}a" vs "$#a" } if ($prefix eq '$#') { return "\$#{$name}"; } } return $prefix . $self->maybe_qualify($prefix, $name); } my %unctrl = # portable to EBCDIC ( "\c@" => '@', # unused "\cA" => 'A', "\cB" => 'B', "\cC" => 'C', "\cD" => 'D', "\cE" => 'E', "\cF" => 'F', "\cG" => 'G', "\cH" => 'H', "\cI" => 'I', "\cJ" => 'J', "\cK" => 'K', "\cL" => 'L', "\cM" => 'M', "\cN" => 'N', "\cO" => 'O', "\cP" => 'P', "\cQ" => 'Q', "\cR" => 'R', "\cS" => 'S', "\cT" => 'T', "\cU" => 'U', "\cV" => 'V', "\cW" => 'W', "\cX" => 'X', "\cY" => 'Y', "\cZ" => 'Z', "\c[" => '[', # unused "\c\\" => '\\', # unused "\c]" => ']', # unused "\c_" => '_', # unused ); # Return just the name, without the prefix. It may be returned as a quoted # string. The second return value is a boolean indicating that. sub stash_variable_name { my($self, $prefix, $gv) = @_; my $name = $self->gv_name($gv, 1); $name = $self->maybe_qualify($prefix,$name); if ($name =~ /^(?:\S|(?!\d)[\ca-\cz]?(?:\w|::)*|\d+)\z/) { $name =~ s/^([\ca-\cz])/'^' . $unctrl{$1}/e; $name =~ /^(\^..|{)/ and $name = "{$name}"; return $name, 0; # not quoted } else { single_delim("q", "'", $name, $self), 1; } } sub maybe_qualify { my ($self,$prefix,$name) = @_; my $v = ($prefix eq '$#' ? '@' : $prefix) . $name; if ($prefix eq "") { $name .= "::" if $name =~ /(?:\ACORE::[^:]*|::)\z/; return $name; } return $name if $name =~ /::/; return $self->{'curstash'}.'::'. $name if $name =~ /^(?!\d)\w/ # alphabetic && $v !~ /^\$[ab]\z/ # not $a or $b && $v =~ /\A[\$\@\%\&]/ # scalar, array, hash, or sub && !$globalnames{$name} # not a global name && $self->{hints} & $strict_bits{vars} # strict vars && !$self->lex_in_scope($v,1) # no "our" or $self->lex_in_scope($v); # conflicts with "my" variable return $name; } sub lex_in_scope { my ($self, $name, $our) = @_; substr $name, 0, 0, = $our ? 'o' : 'm'; # our/my $self->populate_curcvlex() if !defined $self->{'curcvlex'}; return 0 if !defined($self->{'curcop'}); my $seq = $self->{'curcop'}->cop_seq; return 0 if !exists $self->{'curcvlex'}{$name}; for my $a (@{$self->{'curcvlex'}{$name}}) { my ($st, $en) = @$a; return 1 if $seq > $st && $seq <= $en; } return 0; } sub populate_curcvlex { my $self = shift; for (my $cv = $self->{'curcv'}; class($cv) eq "CV"; $cv = $cv->OUTSIDE) { my $padlist = $cv->PADLIST; # an undef CV still in lexical chain next if class($padlist) eq "SPECIAL"; my @padlist = $padlist->ARRAY; my @ns = $padlist[0]->ARRAY; for (my $i=0; $i<@ns; ++$i) { next if class($ns[$i]) eq "SPECIAL"; if (class($ns[$i]) eq "PV") { # Probably that pesky lexical @_ next; } my $name = $ns[$i]->PVX; next unless defined $name; my ($seq_st, $seq_en) = ($ns[$i]->FLAGS & SVf_FAKE) ? (0, 999999) : ($ns[$i]->COP_SEQ_RANGE_LOW, $ns[$i]->COP_SEQ_RANGE_HIGH); push @{$self->{'curcvlex'}{ ($ns[$i]->FLAGS & SVpad_OUR ? 'o' : 'm') . $name }}, [$seq_st, $seq_en, $ns[$i]]; } } } sub find_scope_st { ((find_scope(@_))[0]); } sub find_scope_en { ((find_scope(@_))[1]); } # Recurses down the tree, looking for pad variable introductions and COPs sub find_scope { my ($self, $op, $scope_st, $scope_en) = @_; carp("Undefined op in find_scope") if !defined $op; return ($scope_st, $scope_en) unless $op->flags & OPf_KIDS; my @queue = ($op); while(my $op = shift @queue ) { for (my $o=$op->first; $$o; $o=$o->sibling) { if ($o->name =~ /^pad.v$/ && $o->private & OPpLVAL_INTRO) { my $s = int($self->padname_sv($o->targ)->COP_SEQ_RANGE_LOW); my $e = $self->padname_sv($o->targ)->COP_SEQ_RANGE_HIGH; $scope_st = $s if !defined($scope_st) || $s < $scope_st; $scope_en = $e if !defined($scope_en) || $e > $scope_en; return ($scope_st, $scope_en); } elsif (is_state($o)) { my $c = $o->cop_seq; $scope_st = $c if !defined($scope_st) || $c < $scope_st; $scope_en = $c if !defined($scope_en) || $c > $scope_en; return ($scope_st, $scope_en); } elsif ($o->flags & OPf_KIDS) { unshift (@queue, $o); } } } return ($scope_st, $scope_en); } # Returns a list of subs which should be inserted before the COP sub cop_subs { my ($self, $op, $out_seq) = @_; my $seq = $op->cop_seq; $seq = $out_seq if defined($out_seq) && $out_seq < $seq; return $self->seq_subs($seq); } sub seq_subs { my ($self, $seq) = @_; my @text; #push @text, "# ($seq)\n"; return "" if !defined $seq; my @pending; while (scalar(@{$self->{'subs_todo'}}) and $seq > $self->{'subs_todo'}[0][0]) { my $cv = $self->{'subs_todo'}[0][1]; # Skip the OUTSIDE check for lexical subs. We may be deparsing a # cloned anon sub with lexical subs declared in it, in which case # the OUTSIDE pointer points to the anon protosub. my $lexical = ref $self->{'subs_todo'}[0][3]; my $outside = !$lexical && $cv && $cv->OUTSIDE; if (!$lexical and $cv and ${$cv->OUTSIDE || \0} != ${$self->{'curcv'}}) { push @pending, shift @{$self->{'subs_todo'}}; next; } push @text, $self->next_todo; } unshift @{$self->{'subs_todo'}}, @pending; return @text; } sub _features_from_bundle { my ($hints, $hh) = @_; foreach (@{$feature::feature_bundle{@feature::hint_bundles[$hints >> $feature::hint_shift]}}) { $hh->{$feature::feature{$_}} = 1; } return $hh; } # generate any pragmas, 'package foo' etc needed to synchronise # with the given cop sub pragmata { my $self = shift; my($op) = @_; my @text; my $stash = $op->stashpv; if ($stash ne $self->{'curstash'}) { push @text, $self->keyword("package") . " $stash;\n"; $self->{'curstash'} = $stash; } if (OPpCONST_ARYBASE && $self->{'arybase'} != $op->arybase) { push @text, '$[ = '. $op->arybase .";\n"; $self->{'arybase'} = $op->arybase; } my $warnings = $op->warnings; my $warning_bits; if ($warnings->isa("B::SPECIAL") && $$warnings == 4) { $warning_bits = $warnings::Bits{"all"} & WARN_MASK; } elsif ($warnings->isa("B::SPECIAL") && $$warnings == 5) { $warning_bits = $warnings::NONE; } elsif ($warnings->isa("B::SPECIAL")) { $warning_bits = undef; } else { $warning_bits = $warnings->PV & WARN_MASK; } if (defined ($warning_bits) and !defined($self->{warnings}) || $self->{'warnings'} ne $warning_bits) { push @text, $self->declare_warnings($self->{'warnings'}, $warning_bits); $self->{'warnings'} = $warning_bits; } my $hints = $op->hints; my $old_hints = $self->{'hints'}; if ($self->{'hints'} != $hints) { push @text, $self->declare_hints($self->{'hints'}, $hints); $self->{'hints'} = $hints; } my $newhh; $newhh = $op->hints_hash->HASH; { # feature bundle hints my $from = $old_hints & $feature::hint_mask; my $to = $ hints & $feature::hint_mask; if ($from != $to) { if ($to == $feature::hint_mask) { if ($self->{'hinthash'}) { delete $self->{'hinthash'}{$_} for grep /^feature_/, keys %{$self->{'hinthash'}}; } else { $self->{'hinthash'} = {} } $self->{'hinthash'} = _features_from_bundle($from, $self->{'hinthash'}); } else { my $bundle = $feature::hint_bundles[$to >> $feature::hint_shift]; $bundle =~ s/(\d[13579])\z/$1+1/e; # 5.11 => 5.12 push @text, $self->keyword("no") . " feature ':all';\n", $self->keyword("use") . " feature ':$bundle';\n"; } } } { push @text, $self->declare_hinthash( $self->{'hinthash'}, $newhh, $self->{indent_size}, $self->{hints}, ); $self->{'hinthash'} = $newhh; } return join("", @text); } # Notice how subs and formats are inserted between statements here; # also $[ assignments and pragmas. sub pp_nextstate { my $self = shift; my($op, $cx) = @_; $self->{'curcop'} = $op; my @text; my @subs = $self->cop_subs($op); if (@subs) { # Special marker to swallow up the semicolon push @subs, "\cK"; } push @text, @subs; push @text, $self->pragmata($op); # This should go after of any branches that add statements, to # increase the chances that it refers to the same line it did in # the original program. if ($self->{'linenums'} && $cx != .5) { # $cx == .5 means in a format push @text, "\f#line " . $op->line . ' "' . $op->file, qq'"\n'; } push @text, $op->label . ": " if $op->label; return join("", @text); } sub declare_warnings { my ($self, $from, $to) = @_; $from //= ''; my $all = (warnings::bits("all") & WARN_MASK); unless ((($from & WARN_MASK) & ~$all) =~ /[^\0]/) { # no FATAL bits need turning off if ( ($to & WARN_MASK) eq $all) { return $self->keyword("use") . " warnings;\n"; } elsif (($to & WARN_MASK) eq ("\0"x length($to) & WARN_MASK)) { return $self->keyword("no") . " warnings;\n"; } } return "BEGIN {\${^WARNING_BITS} = \"" . join("", map { sprintf("\\x%02x", ord $_) } split "", $to) . "\"}\n\cK"; } sub declare_hints { my ($self, $from, $to) = @_; my $use = $to & ~$from; my $no = $from & ~$to; my $decls = ""; for my $pragma (hint_pragmas($use)) { $decls .= $self->keyword("use") . " $pragma;\n"; } for my $pragma (hint_pragmas($no)) { $decls .= $self->keyword("no") . " $pragma;\n"; } return $decls; } # Internal implementation hints that the core sets automatically, so don't need # (or want) to be passed back to the user my %ignored_hints = ( 'open<' => 1, 'open>' => 1, ':' => 1, 'strict/refs' => 1, 'strict/subs' => 1, 'strict/vars' => 1, ); my %rev_feature; sub declare_hinthash { my ($self, $from, $to, $indent, $hints) = @_; my $doing_features = ($hints & $feature::hint_mask) == $feature::hint_mask; my @decls; my @features; my @unfeatures; # bugs? for my $key (sort keys %$to) { next if $ignored_hints{$key}; my $is_feature = $key =~ /^feature_/; next if $is_feature and not $doing_features; if (!exists $from->{$key} or $from->{$key} ne $to->{$key}) { push(@features, $key), next if $is_feature; push @decls, qq(\$^H{) . single_delim("q", "'", $key, $self) . qq(} = ) . ( defined $to->{$key} ? single_delim("q", "'", $to->{$key}, $self) : 'undef' ) . qq(;); } } for my $key (sort keys %$from) { next if $ignored_hints{$key}; my $is_feature = $key =~ /^feature_/; next if $is_feature and not $doing_features; if (!exists $to->{$key}) { push(@unfeatures, $key), next if $is_feature; push @decls, qq(delete \$^H{'$key'};); } } my @ret; if (@features || @unfeatures) { if (!%rev_feature) { %rev_feature = reverse %feature::feature } } if (@features) { push @ret, $self->keyword("use") . " feature " . join(", ", map "'$rev_feature{$_}'", @features) . ";\n"; } if (@unfeatures) { push @ret, $self->keyword("no") . " feature " . join(", ", map "'$rev_feature{$_}'", @unfeatures) . ";\n"; } @decls and push @ret, join("\n" . (" " x $indent), "BEGIN {", @decls) . "\n}\n\cK"; return @ret; } sub hint_pragmas { my ($bits) = @_; my (@pragmas, @strict); push @pragmas, "integer" if $bits & 0x1; for (sort keys %strict_bits) { push @strict, "'$_'" if $bits & $strict_bits{$_}; } if (@strict == keys %strict_bits) { push @pragmas, "strict"; } elsif (@strict) { push @pragmas, "strict " . join ', ', @strict; } push @pragmas, "bytes" if $bits & 0x8; return @pragmas; } sub pp_dbstate { pp_nextstate(@_) } sub pp_setstate { pp_nextstate(@_) } sub pp_unstack { return "" } # see also leaveloop my %feature_keywords = ( # keyword => 'feature', state => 'state', say => 'say', given => 'switch', when => 'switch', default => 'switch', break => 'switch', evalbytes=>'evalbytes', __SUB__ => '__SUB__', fc => 'fc', ); # keywords that are strong and also have a prototype # my %strong_proto_keywords = map { $_ => 1 } qw( pos prototype scalar study undef ); sub feature_enabled { my($self,$name) = @_; my $hh; my $hints = $self->{hints} & $feature::hint_mask; if ($hints && $hints != $feature::hint_mask) { $hh = _features_from_bundle($hints); } elsif ($hints) { $hh = $self->{'hinthash'} } return $hh && $hh->{"feature_$feature_keywords{$name}"} } sub keyword { my $self = shift; my $name = shift; return $name if $name =~ /^CORE::/; # just in case if (exists $feature_keywords{$name}) { return "CORE::$name" if not $self->feature_enabled($name); } # This sub may be called for a program that has no nextstate ops. In # that case we may have a lexical sub named no/use/sub in scope but # but $self->lex_in_scope will return false because it depends on the # current nextstate op. So we need this alternate method if there is # no current cop. if (!$self->{'curcop'}) { $self->populate_curcvlex() if !defined $self->{'curcvlex'}; return "CORE::$name" if exists $self->{'curcvlex'}{"m&$name"} || exists $self->{'curcvlex'}{"o&$name"}; } elsif ($self->lex_in_scope("&$name") || $self->lex_in_scope("&$name", 1)) { return "CORE::$name"; } if ($strong_proto_keywords{$name} || ($name !~ /^(?:chom?p|do|exec|glob|s(?:elect|ystem))\z/ && !defined eval{prototype "CORE::$name"}) ) { return $name } if ( exists $self->{subs_declared}{$name} or exists &{"$self->{curstash}::$name"} ) { return "CORE::$name" } return $name; } sub baseop { my $self = shift; my($op, $cx, $name) = @_; return $self->keyword($name); } sub pp_stub { "()" } sub pp_wantarray { baseop(@_, "wantarray") } sub pp_fork { baseop(@_, "fork") } sub pp_wait { maybe_targmy(@_, \&baseop, "wait") } sub pp_getppid { maybe_targmy(@_, \&baseop, "getppid") } sub pp_time { maybe_targmy(@_, \&baseop, "time") } sub pp_tms { baseop(@_, "times") } sub pp_ghostent { baseop(@_, "gethostent") } sub pp_gnetent { baseop(@_, "getnetent") } sub pp_gprotoent { baseop(@_, "getprotoent") } sub pp_gservent { baseop(@_, "getservent") } sub pp_ehostent { baseop(@_, "endhostent") } sub pp_enetent { baseop(@_, "endnetent") } sub pp_eprotoent { baseop(@_, "endprotoent") } sub pp_eservent { baseop(@_, "endservent") } sub pp_gpwent { baseop(@_, "getpwent") } sub pp_spwent { baseop(@_, "setpwent") } sub pp_epwent { baseop(@_, "endpwent") } sub pp_ggrent { baseop(@_, "getgrent") } sub pp_sgrent { baseop(@_, "setgrent") } sub pp_egrent { baseop(@_, "endgrent") } sub pp_getlogin { baseop(@_, "getlogin") } sub POSTFIX () { 1 } # I couldn't think of a good short name, but this is the category of # symbolic unary operators with interesting precedence sub pfixop { my $self = shift; my($op, $cx, $name, $prec, $flags) = (@_, 0); my $kid = $op->first; $kid = $self->deparse($kid, $prec); return $self->maybe_parens(($flags & POSTFIX) ? "$kid$name" # avoid confusion with filetests : $name eq '-' && $kid =~ /^[a-zA-Z](?!\w)/ ? "$name($kid)" : "$name$kid", $cx, $prec); } sub pp_preinc { pfixop(@_, "++", 23) } sub pp_predec { pfixop(@_, "--", 23) } sub pp_postinc { maybe_targmy(@_, \&pfixop, "++", 23, POSTFIX) } sub pp_postdec { maybe_targmy(@_, \&pfixop, "--", 23, POSTFIX) } sub pp_i_preinc { pfixop(@_, "++", 23) } sub pp_i_predec { pfixop(@_, "--", 23) } sub pp_i_postinc { maybe_targmy(@_, \&pfixop, "++", 23, POSTFIX) } sub pp_i_postdec { maybe_targmy(@_, \&pfixop, "--", 23, POSTFIX) } sub pp_complement { maybe_targmy(@_, \&pfixop, "~", 21) } *pp_ncomplement = *pp_complement; sub pp_scomplement { maybe_targmy(@_, \&pfixop, "~.", 21) } sub pp_negate { maybe_targmy(@_, \&real_negate) } sub real_negate { my $self = shift; my($op, $cx) = @_; if ($op->first->name =~ /^(i_)?negate$/) { # avoid --$x $self->pfixop($op, $cx, "-", 21.5); } else { $self->pfixop($op, $cx, "-", 21); } } sub pp_i_negate { pp_negate(@_) } sub pp_not { my $self = shift; my($op, $cx) = @_; if ($cx <= 4) { $self->listop($op, $cx, "not", $op->first); } else { $self->pfixop($op, $cx, "!", 21); } } sub unop { my $self = shift; my($op, $cx, $name, $nollafr) = @_; my $kid; if ($op->flags & OPf_KIDS) { $kid = $op->first; if (not $name) { # this deals with 'boolkeys' right now return $self->deparse($kid,$cx); } my $builtinname = $name; $builtinname =~ /^CORE::/ or $builtinname = "CORE::$name"; if (defined prototype($builtinname) && $builtinname ne 'CORE::readline' && prototype($builtinname) =~ /^;?\*/ && $kid->name eq "rv2gv") { $kid = $kid->first; } if ($nollafr) { if (($kid = $self->deparse($kid, 16)) !~ s/^\cS//) { # require foo() is a syntax error. $kid =~ /^(?!\d)\w/ and $kid = "($kid)"; } return $self->maybe_parens( $self->keyword($name) . " $kid", $cx, 16 ); } return $self->maybe_parens_unop($name, $kid, $cx); } else { return $self->maybe_parens( $self->keyword($name) . ($op->flags & OPf_SPECIAL ? "()" : ""), $cx, 16, ); } } sub pp_chop { maybe_targmy(@_, \&unop, "chop") } sub pp_chomp { maybe_targmy(@_, \&unop, "chomp") } sub pp_schop { maybe_targmy(@_, \&unop, "chop") } sub pp_schomp { maybe_targmy(@_, \&unop, "chomp") } sub pp_defined { unop(@_, "defined") } sub pp_undef { unop(@_, "undef") } sub pp_study { unop(@_, "study") } sub pp_ref { unop(@_, "ref") } sub pp_pos { maybe_local(@_, unop(@_, "pos")) } sub pp_sin { maybe_targmy(@_, \&unop, "sin") } sub pp_cos { maybe_targmy(@_, \&unop, "cos") } sub pp_rand { maybe_targmy(@_, \&unop, "rand") } sub pp_srand { unop(@_, "srand") } sub pp_exp { maybe_targmy(@_, \&unop, "exp") } sub pp_log { maybe_targmy(@_, \&unop, "log") } sub pp_sqrt { maybe_targmy(@_, \&unop, "sqrt") } sub pp_int { maybe_targmy(@_, \&unop, "int") } sub pp_hex { maybe_targmy(@_, \&unop, "hex") } sub pp_oct { maybe_targmy(@_, \&unop, "oct") } sub pp_abs { maybe_targmy(@_, \&unop, "abs") } sub pp_length { maybe_targmy(@_, \&unop, "length") } sub pp_ord { maybe_targmy(@_, \&unop, "ord") } sub pp_chr { maybe_targmy(@_, \&unop, "chr") } sub pp_each { unop(@_, "each") } sub pp_values { unop(@_, "values") } sub pp_keys { unop(@_, "keys") } { no strict 'refs'; *{"pp_r$_"} = *{"pp_$_"} for qw< keys each values >; } sub pp_boolkeys { # no name because its an optimisation op that has no keyword unop(@_,""); } sub pp_aeach { unop(@_, "each") } sub pp_avalues { unop(@_, "values") } sub pp_akeys { unop(@_, "keys") } sub pp_pop { unop(@_, "pop") } sub pp_shift { unop(@_, "shift") } sub pp_caller { unop(@_, "caller") } sub pp_reset { unop(@_, "reset") } sub pp_exit { unop(@_, "exit") } sub pp_prototype { unop(@_, "prototype") } sub pp_close { unop(@_, "close") } sub pp_fileno { unop(@_, "fileno") } sub pp_umask { unop(@_, "umask") } sub pp_untie { unop(@_, "untie") } sub pp_tied { unop(@_, "tied") } sub pp_dbmclose { unop(@_, "dbmclose") } sub pp_getc { unop(@_, "getc") } sub pp_eof { unop(@_, "eof") } sub pp_tell { unop(@_, "tell") } sub pp_getsockname { unop(@_, "getsockname") } sub pp_getpeername { unop(@_, "getpeername") } sub pp_chdir { my ($self, $op, $cx) = @_; if (($op->flags & (OPf_SPECIAL|OPf_KIDS)) == (OPf_SPECIAL|OPf_KIDS)) { my $kw = $self->keyword("chdir"); my $kid = $self->const_sv($op->first)->PV; my $code = $kw . ($cx >= 16 || $self->{'parens'} ? "($kid)" : " $kid"); maybe_targmy(@_, sub { $_[3] }, $code); } else { maybe_targmy(@_, \&unop, "chdir") } } sub pp_chroot { maybe_targmy(@_, \&unop, "chroot") } sub pp_readlink { unop(@_, "readlink") } sub pp_rmdir { maybe_targmy(@_, \&unop, "rmdir") } sub pp_readdir { unop(@_, "readdir") } sub pp_telldir { unop(@_, "telldir") } sub pp_rewinddir { unop(@_, "rewinddir") } sub pp_closedir { unop(@_, "closedir") } sub pp_getpgrp { maybe_targmy(@_, \&unop, "getpgrp") } sub pp_localtime { unop(@_, "localtime") } sub pp_gmtime { unop(@_, "gmtime") } sub pp_alarm { unop(@_, "alarm") } sub pp_sleep { maybe_targmy(@_, \&unop, "sleep") } sub pp_dofile { my $code = unop(@_, "do", 1); # llafr does not apply if ($code =~ s/^((?:CORE::)?do) \{/$1({/) { $code .= ')' } $code; } sub pp_entereval { unop( @_, $_[1]->private & OPpEVAL_BYTES ? 'evalbytes' : "eval" ) } sub pp_ghbyname { unop(@_, "gethostbyname") } sub pp_gnbyname { unop(@_, "getnetbyname") } sub pp_gpbyname { unop(@_, "getprotobyname") } sub pp_shostent { unop(@_, "sethostent") } sub pp_snetent { unop(@_, "setnetent") } sub pp_sprotoent { unop(@_, "setprotoent") } sub pp_sservent { unop(@_, "setservent") } sub pp_gpwnam { unop(@_, "getpwnam") } sub pp_gpwuid { unop(@_, "getpwuid") } sub pp_ggrnam { unop(@_, "getgrnam") } sub pp_ggrgid { unop(@_, "getgrgid") } sub pp_lock { unop(@_, "lock") } sub pp_continue { unop(@_, "continue"); } sub pp_break { unop(@_, "break"); } sub givwhen { my $self = shift; my($op, $cx, $givwhen) = @_; my $enterop = $op->first; my ($head, $block); if ($enterop->flags & OPf_SPECIAL) { $head = $self->keyword("default"); $block = $self->deparse($enterop->first, 0); } else { my $cond = $enterop->first; my $cond_str = $self->deparse($cond, 1); $head = "$givwhen ($cond_str)"; $block = $self->deparse($cond->sibling, 0); } return "$head {\n". "\t$block\n". "\b}\cK"; } sub pp_leavegiven { givwhen(@_, $_[0]->keyword("given")); } sub pp_leavewhen { givwhen(@_, $_[0]->keyword("when")); } sub pp_exists { my $self = shift; my($op, $cx) = @_; my $arg; my $name = $self->keyword("exists"); if ($op->private & OPpEXISTS_SUB) { # Checking for the existence of a subroutine return $self->maybe_parens_func($name, $self->pp_rv2cv($op->first, 16), $cx, 16); } if ($op->flags & OPf_SPECIAL) { # Array element, not hash element return $self->maybe_parens_func($name, $self->pp_aelem($op->first, 16), $cx, 16); } return $self->maybe_parens_func($name, $self->pp_helem($op->first, 16), $cx, 16); } sub pp_delete { my $self = shift; my($op, $cx) = @_; my $arg; my $name = $self->keyword("delete"); if ($op->private & (OPpSLICE|OPpKVSLICE)) { if ($op->flags & OPf_SPECIAL) { # Deleting from an array, not a hash return $self->maybe_parens_func($name, $self->pp_aslice($op->first, 16), $cx, 16); } return $self->maybe_parens_func($name, $self->pp_hslice($op->first, 16), $cx, 16); } else { if ($op->flags & OPf_SPECIAL) { # Deleting from an array, not a hash return $self->maybe_parens_func($name, $self->pp_aelem($op->first, 16), $cx, 16); } return $self->maybe_parens_func($name, $self->pp_helem($op->first, 16), $cx, 16); } } sub pp_require { my $self = shift; my($op, $cx) = @_; my $opname = $op->flags & OPf_SPECIAL ? 'CORE::require' : 'require'; my $kid = $op->first; if ($kid->name eq 'const') { my $priv = $kid->private; my $sv = $self->const_sv($kid); my $arg; if ($priv & OPpCONST_BARE) { $arg = $sv->PV; $arg =~ s[/][::]g; $arg =~ s/\.pm//g; } elsif ($priv & OPpCONST_NOVER) { $opname = $self->keyword('no'); $arg = $self->const($sv, 16); } elsif ((my $tmp = $self->const($sv, 16)) =~ /^v/) { $arg = $tmp; } if ($arg) { return $self->maybe_parens("$opname $arg", $cx, 16); } } $self->unop( $op, $cx, $opname, 1, # llafr does not apply ); } sub pp_scalar { my $self = shift; my($op, $cx) = @_; my $kid = $op->first; if (not null $kid->sibling) { # XXX Was a here-doc return $self->dquote($op); } $self->unop(@_, "scalar"); } sub padval { my $self = shift; my $targ = shift; return $self->{'curcv'}->PADLIST->ARRAYelt(1)->ARRAYelt($targ); } sub anon_hash_or_list { my $self = shift; my($op, $cx) = @_; my($pre, $post) = @{{"anonlist" => ["[","]"], "anonhash" => ["{","}"]}->{$op->name}}; my($expr, @exprs); $op = $op->first->sibling; # skip pushmark for (; !null($op); $op = $op->sibling) { $expr = $self->deparse($op, 6); push @exprs, $expr; } if ($pre eq "{" and $cx < 1) { # Disambiguate that it's not a block $pre = "+{"; } return $pre . join(", ", @exprs) . $post; } sub pp_anonlist { my $self = shift; my ($op, $cx) = @_; if ($op->flags & OPf_SPECIAL) { return $self->anon_hash_or_list($op, $cx); } warn "Unexpected op pp_" . $op->name() . " without OPf_SPECIAL"; return 'XXX'; } *pp_anonhash = \&pp_anonlist; sub pp_refgen { my $self = shift; my($op, $cx) = @_; my $kid = $op->first; if ($kid->name eq "null") { my $anoncode = $kid = $kid->first; if ($anoncode->name eq "anonconst") { $anoncode = $anoncode->first->first->sibling; } if ($anoncode->name eq "anoncode" or !null($anoncode = $kid->sibling) and $anoncode->name eq "anoncode") { return $self->e_anoncode({ code => $self->padval($anoncode->targ) }); } elsif ($kid->name eq "pushmark") { my $sib_name = $kid->sibling->name; if ($sib_name eq 'entersub') { my $text = $self->deparse($kid->sibling, 1); # Always show parens for \(&func()), but only with -p otherwise $text = "($text)" if $self->{'parens'} or $kid->sibling->private & OPpENTERSUB_AMPER; return "\\$text"; } } } local $self->{'in_refgen'} = 1; $self->pfixop($op, $cx, "\\", 20); } sub e_anoncode { my ($self, $info) = @_; my $text = $self->deparse_sub($info->{code}); return $self->keyword("sub") . " $text"; } sub pp_srefgen { pp_refgen(@_) } sub pp_readline { my $self = shift; my($op, $cx) = @_; my $kid = $op->first; if (is_scalar($kid) and $op->flags & OPf_SPECIAL and $self->deparse($kid, 1) eq 'ARGV') { return '<<>>'; } return $self->unop($op, $cx, "readline"); } sub pp_rcatline { my $self = shift; my($op) = @_; return "<" . $self->gv_name($self->gv_or_padgv($op)) . ">"; } # Unary operators that can occur as pseudo-listops inside double quotes sub dq_unop { my $self = shift; my($op, $cx, $name, $prec, $flags) = (@_, 0, 0); my $kid; if ($op->flags & OPf_KIDS) { $kid = $op->first; # If there's more than one kid, the first is an ex-pushmark. $kid = $kid->sibling if not null $kid->sibling; return $self->maybe_parens_unop($name, $kid, $cx); } else { return $name . ($op->flags & OPf_SPECIAL ? "()" : ""); } } sub pp_ucfirst { dq_unop(@_, "ucfirst") } sub pp_lcfirst { dq_unop(@_, "lcfirst") } sub pp_uc { dq_unop(@_, "uc") } sub pp_lc { dq_unop(@_, "lc") } sub pp_quotemeta { maybe_targmy(@_, \&dq_unop, "quotemeta") } sub pp_fc { dq_unop(@_, "fc") } sub loopex { my $self = shift; my ($op, $cx, $name) = @_; if (class($op) eq "PVOP") { $name .= " " . $op->pv; } elsif (class($op) eq "OP") { # no-op } elsif (class($op) eq "UNOP") { (my $kid = $self->deparse($op->first, 7)) =~ s/^\cS//; # last foo() is a syntax error. $kid =~ /^(?!\d)\w/ and $kid = "($kid)"; $name .= " $kid"; } return $self->maybe_parens($name, $cx, 7); } sub pp_last { loopex(@_, "last") } sub pp_next { loopex(@_, "next") } sub pp_redo { loopex(@_, "redo") } sub pp_goto { loopex(@_, "goto") } sub pp_dump { loopex(@_, "CORE::dump") } sub ftst { my $self = shift; my($op, $cx, $name) = @_; if (class($op) eq "UNOP") { # Genuine '-X' filetests are exempt from the LLAFR, but not # l?stat() if ($name =~ /^-/) { (my $kid = $self->deparse($op->first, 16)) =~ s/^\cS//; return $self->maybe_parens("$name $kid", $cx, 16); } return $self->maybe_parens_unop($name, $op->first, $cx); } elsif (class($op) =~ /^(SV|PAD)OP$/) { return $self->maybe_parens_func($name, $self->pp_gv($op, 1), $cx, 16); } else { # I don't think baseop filetests ever survive ck_ftst, but... return $name; } } sub pp_lstat { ftst(@_, "lstat") } sub pp_stat { ftst(@_, "stat") } sub pp_ftrread { ftst(@_, "-R") } sub pp_ftrwrite { ftst(@_, "-W") } sub pp_ftrexec { ftst(@_, "-X") } sub pp_fteread { ftst(@_, "-r") } sub pp_ftewrite { ftst(@_, "-w") } sub pp_fteexec { ftst(@_, "-x") } sub pp_ftis { ftst(@_, "-e") } sub pp_fteowned { ftst(@_, "-O") } sub pp_ftrowned { ftst(@_, "-o") } sub pp_ftzero { ftst(@_, "-z") } sub pp_ftsize { ftst(@_, "-s") } sub pp_ftmtime { ftst(@_, "-M") } sub pp_ftatime { ftst(@_, "-A") } sub pp_ftctime { ftst(@_, "-C") } sub pp_ftsock { ftst(@_, "-S") } sub pp_ftchr { ftst(@_, "-c") } sub pp_ftblk { ftst(@_, "-b") } sub pp_ftfile { ftst(@_, "-f") } sub pp_ftdir { ftst(@_, "-d") } sub pp_ftpipe { ftst(@_, "-p") } sub pp_ftlink { ftst(@_, "-l") } sub pp_ftsuid { ftst(@_, "-u") } sub pp_ftsgid { ftst(@_, "-g") } sub pp_ftsvtx { ftst(@_, "-k") } sub pp_fttty { ftst(@_, "-t") } sub pp_fttext { ftst(@_, "-T") } sub pp_ftbinary { ftst(@_, "-B") } sub SWAP_CHILDREN () { 1 } sub ASSIGN () { 2 } # has OP= variant sub LIST_CONTEXT () { 4 } # Assignment is in list context my(%left, %right); sub assoc_class { my $op = shift; my $name = $op->name; if ($name eq "concat" and $op->first->name eq "concat") { # avoid spurious '=' -- see comment in pp_concat return "concat"; } if ($name eq "null" and class($op) eq "UNOP" and $op->first->name =~ /^(and|x?or)$/ and null $op->first->sibling) { # Like all conditional constructs, OP_ANDs and OP_ORs are topped # with a null that's used as the common end point of the two # flows of control. For precedence purposes, ignore it. # (COND_EXPRs have these too, but we don't bother with # their associativity). return assoc_class($op->first); } return $name . ($op->flags & OPf_STACKED ? "=" : ""); } # Left associative operators, like '+', for which # $a + $b + $c is equivalent to ($a + $b) + $c BEGIN { %left = ('multiply' => 19, 'i_multiply' => 19, 'divide' => 19, 'i_divide' => 19, 'modulo' => 19, 'i_modulo' => 19, 'repeat' => 19, 'add' => 18, 'i_add' => 18, 'subtract' => 18, 'i_subtract' => 18, 'concat' => 18, 'left_shift' => 17, 'right_shift' => 17, 'bit_and' => 13, 'nbit_and' => 13, 'sbit_and' => 13, 'bit_or' => 12, 'bit_xor' => 12, 'sbit_or' => 12, 'sbit_xor' => 12, 'nbit_or' => 12, 'nbit_xor' => 12, 'and' => 3, 'or' => 2, 'xor' => 2, ); } sub deparse_binop_left { my $self = shift; my($op, $left, $prec) = @_; if ($left{assoc_class($op)} && $left{assoc_class($left)} and $left{assoc_class($op)} == $left{assoc_class($left)}) { return $self->deparse($left, $prec - .00001); } else { return $self->deparse($left, $prec); } } # Right associative operators, like '=', for which # $a = $b = $c is equivalent to $a = ($b = $c) BEGIN { %right = ('pow' => 22, 'sassign=' => 7, 'aassign=' => 7, 'multiply=' => 7, 'i_multiply=' => 7, 'divide=' => 7, 'i_divide=' => 7, 'modulo=' => 7, 'i_modulo=' => 7, 'repeat=' => 7, 'refassign' => 7, 'refassign=' => 7, 'add=' => 7, 'i_add=' => 7, 'subtract=' => 7, 'i_subtract=' => 7, 'concat=' => 7, 'left_shift=' => 7, 'right_shift=' => 7, 'bit_and=' => 7, 'sbit_and=' => 7, 'nbit_and=' => 7, 'nbit_or=' => 7, 'nbit_xor=' => 7, 'sbit_or=' => 7, 'sbit_xor=' => 7, 'andassign' => 7, 'orassign' => 7, ); } sub deparse_binop_right { my $self = shift; my($op, $right, $prec) = @_; if ($right{assoc_class($op)} && $right{assoc_class($right)} and $right{assoc_class($op)} == $right{assoc_class($right)}) { return $self->deparse($right, $prec - .00001); } else { return $self->deparse($right, $prec); } } sub binop { my $self = shift; my ($op, $cx, $opname, $prec, $flags) = (@_, 0); my $left = $op->first; my $right = $op->last; my $eq = ""; if ($op->flags & OPf_STACKED && $flags & ASSIGN) { $eq = "="; $prec = 7; } if ($flags & SWAP_CHILDREN) { ($left, $right) = ($right, $left); } my $leftop = $left; $left = $self->deparse_binop_left($op, $left, $prec); $left = "($left)" if $flags & LIST_CONTEXT and $left !~ /^(my|our|local|state|)\s*[\@%\(]/ || do { # Parenthesize if the left argument is a # lone repeat op. my $left = $leftop->first->sibling; $left->name eq 'repeat' && null($left->sibling); }; $right = $self->deparse_binop_right($op, $right, $prec); return $self->maybe_parens("$left $opname$eq $right", $cx, $prec); } sub pp_add { maybe_targmy(@_, \&binop, "+", 18, ASSIGN) } sub pp_multiply { maybe_targmy(@_, \&binop, "*", 19, ASSIGN) } sub pp_subtract { maybe_targmy(@_, \&binop, "-",18, ASSIGN) } sub pp_divide { maybe_targmy(@_, \&binop, "/", 19, ASSIGN) } sub pp_modulo { maybe_targmy(@_, \&binop, "%", 19, ASSIGN) } sub pp_i_add { maybe_targmy(@_, \&binop, "+", 18, ASSIGN) } sub pp_i_multiply { maybe_targmy(@_, \&binop, "*", 19, ASSIGN) } sub pp_i_subtract { maybe_targmy(@_, \&binop, "-", 18, ASSIGN) } sub pp_i_divide { maybe_targmy(@_, \&binop, "/", 19, ASSIGN) } sub pp_i_modulo { maybe_targmy(@_, \&binop, "%", 19, ASSIGN) } sub pp_pow { maybe_targmy(@_, \&binop, "**", 22, ASSIGN) } sub pp_left_shift { maybe_targmy(@_, \&binop, "<<", 17, ASSIGN) } sub pp_right_shift { maybe_targmy(@_, \&binop, ">>", 17, ASSIGN) } sub pp_bit_and { maybe_targmy(@_, \&binop, "&", 13, ASSIGN) } sub pp_bit_or { maybe_targmy(@_, \&binop, "|", 12, ASSIGN) } sub pp_bit_xor { maybe_targmy(@_, \&binop, "^", 12, ASSIGN) } *pp_nbit_and = *pp_bit_and; *pp_nbit_or = *pp_bit_or; *pp_nbit_xor = *pp_bit_xor; sub pp_sbit_and { maybe_targmy(@_, \&binop, "&.", 13, ASSIGN) } sub pp_sbit_or { maybe_targmy(@_, \&binop, "|.", 12, ASSIGN) } sub pp_sbit_xor { maybe_targmy(@_, \&binop, "^.", 12, ASSIGN) } sub pp_eq { binop(@_, "==", 14) } sub pp_ne { binop(@_, "!=", 14) } sub pp_lt { binop(@_, "<", 15) } sub pp_gt { binop(@_, ">", 15) } sub pp_ge { binop(@_, ">=", 15) } sub pp_le { binop(@_, "<=", 15) } sub pp_ncmp { binop(@_, "<=>", 14) } sub pp_i_eq { binop(@_, "==", 14) } sub pp_i_ne { binop(@_, "!=", 14) } sub pp_i_lt { binop(@_, "<", 15) } sub pp_i_gt { binop(@_, ">", 15) } sub pp_i_ge { binop(@_, ">=", 15) } sub pp_i_le { binop(@_, "<=", 15) } sub pp_i_ncmp { maybe_targmy(@_, \&binop, "<=>", 14) } sub pp_seq { binop(@_, "eq", 14) } sub pp_sne { binop(@_, "ne", 14) } sub pp_slt { binop(@_, "lt", 15) } sub pp_sgt { binop(@_, "gt", 15) } sub pp_sge { binop(@_, "ge", 15) } sub pp_sle { binop(@_, "le", 15) } sub pp_scmp { maybe_targmy(@_, \&binop, "cmp", 14) } sub pp_sassign { binop(@_, "=", 7, SWAP_CHILDREN) } sub pp_aassign { binop(@_, "=", 7, SWAP_CHILDREN | LIST_CONTEXT) } sub pp_smartmatch { my ($self, $op, $cx) = @_; if (($op->flags & OPf_SPECIAL) && $self->{expand} < 2) { return $self->deparse($op->last, $cx); } else { binop(@_, "~~", 14); } } # '.' is special because concats-of-concats are optimized to save copying # by making all but the first concat stacked. The effect is as if the # programmer had written '($a . $b) .= $c', except legal. sub pp_concat { maybe_targmy(@_, \&real_concat) } sub real_concat { my $self = shift; my($op, $cx) = @_; my $left = $op->first; my $right = $op->last; my $eq = ""; my $prec = 18; if (($op->flags & OPf_STACKED) and !($op->private & OPpCONCAT_NESTED)) { # '.=' rather than optimised '.' $eq = "="; $prec = 7; } $left = $self->deparse_binop_left($op, $left, $prec); $right = $self->deparse_binop_right($op, $right, $prec); return $self->maybe_parens("$left .$eq $right", $cx, $prec); } sub pp_repeat { maybe_targmy(@_, \&repeat) } # 'x' is weird when the left arg is a list sub repeat { my $self = shift; my($op, $cx) = @_; my $left = $op->first; my $right = $op->last; my $eq = ""; my $prec = 19; if ($op->flags & OPf_STACKED) { $eq = "="; $prec = 7; } if (null($right)) { # list repeat; count is inside left-side ex-list # in 5.21.5 and earlier my $kid = $left->first->sibling; # skip pushmark my @exprs; for (; !null($kid->sibling); $kid = $kid->sibling) { push @exprs, $self->deparse($kid, 6); } $right = $kid; $left = "(" . join(", ", @exprs). ")"; } else { my $dolist = $op->private & OPpREPEAT_DOLIST; $left = $self->deparse_binop_left($op, $left, $dolist ? 1 : $prec); if ($dolist) { $left = "($left)"; } } $right = $self->deparse_binop_right($op, $right, $prec); return $self->maybe_parens("$left x$eq $right", $cx, $prec); } sub range { my $self = shift; my ($op, $cx, $type) = @_; my $left = $op->first; my $right = $left->sibling; $left = $self->deparse($left, 9); $right = $self->deparse($right, 9); return $self->maybe_parens("$left $type $right", $cx, 9); } sub pp_flop { my $self = shift; my($op, $cx) = @_; my $flip = $op->first; my $type = ($flip->flags & OPf_SPECIAL) ? "..." : ".."; return $self->range($flip->first, $cx, $type); } # one-line while/until is handled in pp_leave sub logop { my $self = shift; my ($op, $cx, $lowop, $lowprec, $highop, $highprec, $blockname) = @_; my $left = $op->first; my $right = $op->first->sibling; $blockname &&= $self->keyword($blockname); if ($cx < 1 and is_scope($right) and $blockname and $self->{'expand'} < 7) { # if ($a) {$b} $left = $self->deparse($left, 1); $right = $self->deparse($right, 0); return "$blockname ($left) {\n\t$right\n\b}\cK"; } elsif ($cx < 1 and $blockname and not $self->{'parens'} and $self->{'expand'} < 7) { # $b if $a $right = $self->deparse($right, 1); $left = $self->deparse($left, 1); return "$right $blockname $left"; } elsif ($cx > $lowprec and $highop) { # $a && $b $left = $self->deparse_binop_left($op, $left, $highprec); $right = $self->deparse_binop_right($op, $right, $highprec); return $self->maybe_parens("$left $highop $right", $cx, $highprec); } else { # $a and $b $left = $self->deparse_binop_left($op, $left, $lowprec); $right = $self->deparse_binop_right($op, $right, $lowprec); return $self->maybe_parens("$left $lowop $right", $cx, $lowprec); } } sub pp_and { logop(@_, "and", 3, "&&", 11, "if") } sub pp_or { logop(@_, "or", 2, "||", 10, "unless") } sub pp_dor { logop(@_, "//", 10) } # xor is syntactically a logop, but it's really a binop (contrary to # old versions of opcode.pl). Syntax is what matters here. sub pp_xor { logop(@_, "xor", 2, "", 0, "") } sub logassignop { my $self = shift; my ($op, $cx, $opname) = @_; my $left = $op->first; my $right = $op->first->sibling->first; # skip sassign $left = $self->deparse($left, 7); $right = $self->deparse($right, 7); return $self->maybe_parens("$left $opname $right", $cx, 7); } sub pp_andassign { logassignop(@_, "&&=") } sub pp_orassign { logassignop(@_, "||=") } sub pp_dorassign { logassignop(@_, "//=") } sub rv2gv_or_string { my($self,$op) = @_; if ($op->name eq "gv") { # could be open("open") or open("###") my($name,$quoted) = $self->stash_variable_name("", $self->gv_or_padgv($op)); $quoted ? $name : "*$name"; } else { $self->deparse($op, 6); } } sub listop { my $self = shift; my($op, $cx, $name, $kid, $nollafr) = @_; my(@exprs); my $parens = ($cx >= 5) || $self->{'parens'}; $kid ||= $op->first->sibling; # If there are no arguments, add final parentheses (or parenthesize the # whole thing if the llafr does not apply) to account for cases like # (return)+1 or setpgrp()+1. When the llafr does not apply, we use a # precedence of 6 (< comma), as "return, 1" does not need parentheses. if (null $kid) { return $nollafr ? $self->maybe_parens($self->keyword($name), $cx, 7) : $self->keyword($name) . '()' x (7 < $cx); } my $first; my $fullname = $self->keyword($name); my $proto = prototype("CORE::$name"); if ( ( (defined $proto && $proto =~ /^;?\*/) || $name eq 'select' # select(F) doesn't have a proto ) && $kid->name eq "rv2gv" && !($kid->private & OPpLVAL_INTRO) ) { $first = $self->rv2gv_or_string($kid->first); } else { $first = $self->deparse($kid, 6); } if ($name eq "chmod" && $first =~ /^\d+$/) { $first = sprintf("%#o", $first); } $first = "+$first" if not $parens and not $nollafr and substr($first, 0, 1) eq "("; push @exprs, $first; $kid = $kid->sibling; if (defined $proto && $proto =~ /^\*\*/ && $kid->name eq "rv2gv" && !($kid->private & OPpLVAL_INTRO)) { push @exprs, $first = $self->rv2gv_or_string($kid->first); $kid = $kid->sibling; } for (; !null($kid); $kid = $kid->sibling) { push @exprs, $self->deparse($kid, 6); } if ($name eq "reverse" && ($op->private & OPpREVERSE_INPLACE)) { return "$exprs[0] = $fullname" . ($parens ? "($exprs[0])" : " $exprs[0]"); } if ($parens && $nollafr) { return "($fullname " . join(", ", @exprs) . ")"; } elsif ($parens) { return "$fullname(" . join(", ", @exprs) . ")"; } else { return "$fullname " . join(", ", @exprs); } } sub pp_bless { listop(@_, "bless") } sub pp_atan2 { maybe_targmy(@_, \&listop, "atan2") } sub pp_substr { my ($self,$op,$cx) = @_; if ($op->private & OPpSUBSTR_REPL_FIRST) { return listop($self, $op, 7, "substr", $op->first->sibling->sibling) . " = " . $self->deparse($op->first->sibling, 7); } maybe_local(@_, listop(@_, "substr")) } sub pp_index { # Also handles pp_rindex. # # The body of this function includes an unrolled maybe_targmy(), # since the two parts of that sub's actions need to have have the # '== -1' bit in between my($self, $op, $cx) = @_; my $lex = ($op->private & OPpTARGET_MY); my $bool = ($op->private & OPpTRUEBOOL); my $val = $self->listop($op, ($bool ? 14 : $lex ? 7 : $cx), $op->name); # (index() == -1) has op_eq and op_const optimised away if ($bool) { $val .= ($op->private & OPpINDEX_BOOLNEG) ? " == -1" : " != -1"; $val = "($val)" if ($op->flags & OPf_PARENS); } if ($lex) { my $var = $self->padname($op->targ); $val = $self->maybe_parens("$var = $val", $cx, 7); } $val; } sub pp_rindex { pp_index(@_); } sub pp_vec { maybe_targmy(@_, \&maybe_local, listop(@_, "vec")) } sub pp_sprintf { maybe_targmy(@_, \&listop, "sprintf") } sub pp_formline { listop(@_, "formline") } # see also deparse_format sub pp_crypt { maybe_targmy(@_, \&listop, "crypt") } sub pp_unpack { listop(@_, "unpack") } sub pp_pack { listop(@_, "pack") } sub pp_join { maybe_targmy(@_, \&listop, "join") } sub pp_splice { listop(@_, "splice") } sub pp_push { maybe_targmy(@_, \&listop, "push") } sub pp_unshift { maybe_targmy(@_, \&listop, "unshift") } sub pp_reverse { listop(@_, "reverse") } sub pp_warn { listop(@_, "warn") } sub pp_die { listop(@_, "die") } sub pp_return { listop(@_, "return", undef, 1) } # llafr does not apply sub pp_open { listop(@_, "open") } sub pp_pipe_op { listop(@_, "pipe") } sub pp_tie { listop(@_, "tie") } sub pp_binmode { listop(@_, "binmode") } sub pp_dbmopen { listop(@_, "dbmopen") } sub pp_sselect { listop(@_, "select") } sub pp_select { listop(@_, "select") } sub pp_read { listop(@_, "read") } sub pp_sysopen { listop(@_, "sysopen") } sub pp_sysseek { listop(@_, "sysseek") } sub pp_sysread { listop(@_, "sysread") } sub pp_syswrite { listop(@_, "syswrite") } sub pp_send { listop(@_, "send") } sub pp_recv { listop(@_, "recv") } sub pp_seek { listop(@_, "seek") } sub pp_fcntl { listop(@_, "fcntl") } sub pp_ioctl { listop(@_, "ioctl") } sub pp_flock { maybe_targmy(@_, \&listop, "flock") } sub pp_socket { listop(@_, "socket") } sub pp_sockpair { listop(@_, "socketpair") } sub pp_bind { listop(@_, "bind") } sub pp_connect { listop(@_, "connect") } sub pp_listen { listop(@_, "listen") } sub pp_accept { listop(@_, "accept") } sub pp_shutdown { listop(@_, "shutdown") } sub pp_gsockopt { listop(@_, "getsockopt") } sub pp_ssockopt { listop(@_, "setsockopt") } sub pp_chown { maybe_targmy(@_, \&listop, "chown") } sub pp_unlink { maybe_targmy(@_, \&listop, "unlink") } sub pp_chmod { maybe_targmy(@_, \&listop, "chmod") } sub pp_utime { maybe_targmy(@_, \&listop, "utime") } sub pp_rename { maybe_targmy(@_, \&listop, "rename") } sub pp_link { maybe_targmy(@_, \&listop, "link") } sub pp_symlink { maybe_targmy(@_, \&listop, "symlink") } sub pp_mkdir { maybe_targmy(@_, \&listop, "mkdir") } sub pp_open_dir { listop(@_, "opendir") } sub pp_seekdir { listop(@_, "seekdir") } sub pp_waitpid { maybe_targmy(@_, \&listop, "waitpid") } sub pp_system { maybe_targmy(@_, \&indirop, "system") } sub pp_exec { maybe_targmy(@_, \&indirop, "exec") } sub pp_kill { maybe_targmy(@_, \&listop, "kill") } sub pp_setpgrp { maybe_targmy(@_, \&listop, "setpgrp") } sub pp_getpriority { maybe_targmy(@_, \&listop, "getpriority") } sub pp_setpriority { maybe_targmy(@_, \&listop, "setpriority") } sub pp_shmget { listop(@_, "shmget") } sub pp_shmctl { listop(@_, "shmctl") } sub pp_shmread { listop(@_, "shmread") } sub pp_shmwrite { listop(@_, "shmwrite") } sub pp_msgget { listop(@_, "msgget") } sub pp_msgctl { listop(@_, "msgctl") } sub pp_msgsnd { listop(@_, "msgsnd") } sub pp_msgrcv { listop(@_, "msgrcv") } sub pp_semget { listop(@_, "semget") } sub pp_semctl { listop(@_, "semctl") } sub pp_semop { listop(@_, "semop") } sub pp_ghbyaddr { listop(@_, "gethostbyaddr") } sub pp_gnbyaddr { listop(@_, "getnetbyaddr") } sub pp_gpbynumber { listop(@_, "getprotobynumber") } sub pp_gsbyname { listop(@_, "getservbyname") } sub pp_gsbyport { listop(@_, "getservbyport") } sub pp_syscall { listop(@_, "syscall") } sub pp_glob { my $self = shift; my($op, $cx) = @_; my $kid = $op->first->sibling; # skip pushmark my $keyword = $op->flags & OPf_SPECIAL ? 'glob' : $self->keyword('glob'); my $text = $self->deparse($kid); return $cx >= 5 || $self->{'parens'} ? "$keyword($text)" : "$keyword $text"; } # Truncate is special because OPf_SPECIAL makes a bareword first arg # be a filehandle. This could probably be better fixed in the core # by moving the GV lookup into ck_truc. sub pp_truncate { my $self = shift; my($op, $cx) = @_; my(@exprs); my $parens = ($cx >= 5) || $self->{'parens'}; my $kid = $op->first->sibling; my $fh; if ($op->flags & OPf_SPECIAL) { # $kid is an OP_CONST $fh = $self->const_sv($kid)->PV; } else { $fh = $self->deparse($kid, 6); $fh = "+$fh" if not $parens and substr($fh, 0, 1) eq "("; } my $len = $self->deparse($kid->sibling, 6); my $name = $self->keyword('truncate'); if ($parens) { return "$name($fh, $len)"; } else { return "$name $fh, $len"; } } sub indirop { my $self = shift; my($op, $cx, $name) = @_; my($expr, @exprs); my $firstkid = my $kid = $op->first->sibling; my $indir = ""; if ($op->flags & OPf_STACKED) { $indir = $kid; $indir = $indir->first; # skip rv2gv if (is_scope($indir)) { $indir = "{" . $self->deparse($indir, 0) . "}"; $indir = "{;}" if $indir eq "{}"; } elsif ($indir->name eq "const" && $indir->private & OPpCONST_BARE) { $indir = $self->const_sv($indir)->PV; } else { $indir = $self->deparse($indir, 24); } $indir = $indir . " "; $kid = $kid->sibling; } if ($name eq "sort" && $op->private & (OPpSORT_NUMERIC | OPpSORT_INTEGER)) { $indir = ($op->private & OPpSORT_DESCEND) ? '{$b <=> $a} ' : '{$a <=> $b} '; } elsif ($name eq "sort" && $op->private & OPpSORT_DESCEND) { $indir = '{$b cmp $a} '; } for (; !null($kid); $kid = $kid->sibling) { $expr = $self->deparse($kid, !$indir && $kid == $firstkid && $name eq "sort" && $firstkid->name eq "entersub" ? 16 : 6); push @exprs, $expr; } my $name2; if ($name eq "sort" && $op->private & OPpSORT_REVERSE) { $name2 = $self->keyword('reverse') . ' ' . $self->keyword('sort'); } else { $name2 = $self->keyword($name) } if ($name eq "sort" && ($op->private & OPpSORT_INPLACE)) { return "$exprs[0] = $name2 $indir $exprs[0]"; } my $args = $indir . join(", ", @exprs); if ($indir ne "" && $name eq "sort") { # We don't want to say "sort(f 1, 2, 3)", since perl -w will # give bareword warnings in that case. Therefore if context # requires, we'll put parens around the outside "(sort f 1, 2, # 3)". Unfortunately, we'll currently think the parens are # necessary more often that they really are, because we don't # distinguish which side of an assignment we're on. if ($cx >= 5) { return "($name2 $args)"; } else { return "$name2 $args"; } } elsif ( !$indir && $name eq "sort" && !null($op->first->sibling) && $op->first->sibling->name eq 'entersub' ) { # We cannot say sort foo(bar), as foo will be interpreted as a # comparison routine. We have to say sort(...) in that case. return "$name2($args)"; } else { return length $args ? $self->maybe_parens_func($name2, $args, $cx, 5) : $name2 . '()' x (7 < $cx); } } sub pp_prtf { indirop(@_, "printf") } sub pp_print { indirop(@_, "print") } sub pp_say { indirop(@_, "say") } sub pp_sort { indirop(@_, "sort") } sub mapop { my $self = shift; my($op, $cx, $name) = @_; my($expr, @exprs); my $kid = $op->first; # this is the (map|grep)start $kid = $kid->first->sibling; # skip a pushmark my $code = $kid->first; # skip a null if (is_scope $code) { $code = "{" . $self->deparse($code, 0) . "} "; } else { $code = $self->deparse($code, 24); $code .= ", " if !null($kid->sibling); } $kid = $kid->sibling; for (; !null($kid); $kid = $kid->sibling) { $expr = $self->deparse($kid, 6); push @exprs, $expr if defined $expr; } return $self->maybe_parens_func($self->keyword($name), $code . join(", ", @exprs), $cx, 5); } sub pp_mapwhile { mapop(@_, "map") } sub pp_grepwhile { mapop(@_, "grep") } sub pp_mapstart { baseop(@_, "map") } sub pp_grepstart { baseop(@_, "grep") } my %uses_intro; BEGIN { @uses_intro{ eval { require B::Op_private } ? @{$B::Op_private::ops_using{OPpLVAL_INTRO}} : qw(gvsv rv2sv rv2hv rv2gv rv2av aelem helem aslice hslice delete padsv padav padhv enteriter entersub padrange pushmark cond_expr refassign list) } = (); delete @uses_intro{qw( lvref lvrefslice lvavref entersub )}; } # Look for a my/state attribute declaration in a list or ex-list. # Returns undef if not found, 'my($x, @a) :Foo(bar)' etc otherwise. # # There are three basic tree structs that are expected: # # my $x :foo; # <1> ex-list vK/LVINTRO ->c # <0> ex-pushmark v ->3 # <1> entersub[t2] vKRS*/TARG ->b # .... # <0> padsv[$x:64,65] vM/LVINTRO ->c # # my @a :foo; # my %h :foo; # # <1> ex-list vK ->c # <0> ex-pushmark v ->3 # <0> padav[@a:64,65] vM/LVINTRO ->4 # <1> entersub[t2] vKRS*/TARG ->c # .... # # my ($x,@a,%h) :foo; # # <;> nextstate(main 64 -e:1) v:{ ->3 # <@> list vKP ->w # <0> pushmark vM/LVINTRO ->4 # <0> padsv[$x:64,65] vM/LVINTRO ->5 # <0> padav[@a:64,65] vM/LVINTRO ->6 # <0> padhv[%h:64,65] vM/LVINTRO ->7 # <1> entersub[t4] vKRS*/TARG ->f # .... # <1> entersub[t5] vKRS*/TARG ->n # .... # <1> entersub[t6] vKRS*/TARG ->v # .... # where the entersub in all cases looks like # <1> entersub[t2] vKRS*/TARG ->c # <0> pushmark s ->5 # <$> const[PV "attributes"] sM ->6 # <$> const[PV "main"] sM ->7 # <1> srefgen sKM/1 ->9 # <1> ex-list lKRM ->8 # <0> padsv[@a:64,65] sRM ->8 # <$> const[PV "foo"] sM ->a # <.> method_named[PV "import"] ->b sub maybe_var_attr { my ($self, $op, $cx) = @_; my $kid = $op->first->sibling; # skip pushmark return if class($kid) eq 'NULL'; my $lop; my $type; # Extract out all the pad ops and entersub ops into # @padops and @entersubops. Return if anything else seen. # Also determine what class (if any) all the pad vars belong to my $class; my $decl; # 'my' or 'state' my (@padops, @entersubops); for ($lop = $kid; !null($lop); $lop = $lop->sibling) { my $lopname = $lop->name; my $loppriv = $lop->private; if ($lopname =~ /^pad[sah]v$/) { return unless $loppriv & OPpLVAL_INTRO; my $padname = $self->padname_sv($lop->targ); my $thisclass = ($padname->FLAGS & SVpad_TYPED) ? $padname->SvSTASH->NAME : 'main'; # all pad vars must be in the same class $class //= $thisclass; return unless $thisclass eq $class; # all pad vars must be the same sort of declaration # (all my, all state, etc) my $this = ($loppriv & OPpPAD_STATE) ? 'state' : 'my'; if (defined $decl) { return unless $this eq $decl; } $decl = $this; push @padops, $lop; } elsif ($lopname eq 'entersub') { push @entersubops, $lop; } else { return; } } return unless @padops && @padops == @entersubops; # there should be a balance: each padop has a corresponding # 'attributes'->import() method call, in the same order. my @varnames; my $attr_text; for my $i (0..$#padops) { my $padop = $padops[$i]; my $esop = $entersubops[$i]; push @varnames, $self->padname($padop->targ); return unless ($esop->flags & OPf_KIDS); my $kid = $esop->first; return unless $kid->type == OP_PUSHMARK; $kid = $kid->sibling; return unless $$kid && $kid->type == OP_CONST; return unless $self->const_sv($kid)->PV eq 'attributes'; $kid = $kid->sibling; return unless $$kid && $kid->type == OP_CONST; # __PACKAGE__ $kid = $kid->sibling; return unless $$kid && $kid->name eq "srefgen" && ($kid->flags & OPf_KIDS) && ($kid->first->flags & OPf_KIDS) && $kid->first->first->name =~ /^pad[sah]v$/ && $kid->first->first->targ == $padop->targ; $kid = $kid->sibling; my @attr; while ($$kid) { last if ($kid->type != OP_CONST); push @attr, $self->const_sv($kid)->PV; $kid = $kid->sibling; } return unless @attr; my $thisattr = ":" . join(' ', @attr); $attr_text //= $thisattr; # all import calls must have the same list of attributes return unless $attr_text eq $thisattr; return unless $kid->name eq 'method_named'; return unless $self->meth_sv($kid)->PV eq 'import'; $kid = $kid->sibling; return if $$kid; } my $res = $decl; $res .= " $class " if $class ne 'main'; $res .= (@varnames > 1) ? "(" . join(', ', @varnames) . ')' : " $varnames[0]"; return "$res $attr_text"; } sub pp_list { my $self = shift; my($op, $cx) = @_; { # might be my ($s,@a,%h) :Foo(bar); my $my_attr = maybe_var_attr($self, $op, $cx); return $my_attr if defined $my_attr; } my($expr, @exprs); my $kid = $op->first->sibling; # skip pushmark return '' if class($kid) eq 'NULL'; my $lop; my $local = "either"; # could be local(...), my(...), state(...) or our(...) my $type; for ($lop = $kid; !null($lop); $lop = $lop->sibling) { my $lopname = $lop->name; my $loppriv = $lop->private; my $newtype; if ($lopname =~ /^pad[ash]v$/ && $loppriv & OPpLVAL_INTRO) { if ($loppriv & OPpPAD_STATE) { # state() ($local = "", last) if $local !~ /^(?:either|state)$/; $local = "state"; } else { # my() ($local = "", last) if $local !~ /^(?:either|my)$/; $local = "my"; } my $padname = $self->padname_sv($lop->targ); if ($padname->FLAGS & SVpad_TYPED) { $newtype = $padname->SvSTASH->NAME; } } elsif ($lopname =~ /^(?:gv|rv2)([ash])v$/ && $loppriv & OPpOUR_INTRO or $lopname eq "null" && class($lop) eq 'UNOP' && $lop->first->name eq "gvsv" && $lop->first->private & OPpOUR_INTRO) { # our() my $newlocal = "local " x !!($loppriv & OPpLVAL_INTRO) . "our"; ($local = "", last) if $local ne 'either' && $local ne $newlocal; $local = $newlocal; my $funny = !$1 || $1 eq 's' ? '$' : $1 eq 'a' ? '@' : '%'; if (my $t = $self->find_our_type( $funny . $self->gv_or_padgv($lop->first)->NAME )) { $newtype = $t; } } elsif ($lopname ne 'undef' and !($loppriv & OPpLVAL_INTRO) || !exists $uses_intro{$lopname eq 'null' ? substr B::ppname($lop->targ), 3 : $lopname}) { $local = ""; # or not last; } elsif ($lopname ne "undef") { # local() ($local = "", last) if $local !~ /^(?:either|local)$/; $local = "local"; } if (defined $type && defined $newtype && $newtype ne $type) { $local = ''; last; } $type = $newtype; } $local = "" if $local eq "either"; # no point if it's all undefs $local &&= join ' ', map $self->keyword($_), split / /, $local; $local .= " $type " if $local && length $type; return $self->deparse($kid, $cx) if null $kid->sibling and not $local; for (; !null($kid); $kid = $kid->sibling) { if ($local) { if (class($kid) eq "UNOP" and $kid->first->name eq "gvsv") { $lop = $kid->first; } else { $lop = $kid; } $self->{'avoid_local'}{$$lop}++; $expr = $self->deparse($kid, 6); delete $self->{'avoid_local'}{$$lop}; } else { $expr = $self->deparse($kid, 6); } push @exprs, $expr; } if ($local) { if (@exprs == 1 && ($local eq 'state' || $local eq 'CORE::state')) { # 'state @a = ...' is legal, while 'state(@a) = ...' currently isn't return "$local $exprs[0]"; } return "$local(" . join(", ", @exprs) . ")"; } else { return $self->maybe_parens( join(", ", @exprs), $cx, 6); } } sub is_ifelse_cont { my $op = shift; return ($op->name eq "null" and class($op) eq "UNOP" and $op->first->name =~ /^(and|cond_expr)$/ and is_scope($op->first->first->sibling)); } sub pp_cond_expr { my $self = shift; my($op, $cx) = @_; my $cond = $op->first; my $true = $cond->sibling; my $false = $true->sibling; my $cuddle = $self->{'cuddle'}; unless ($cx < 1 and (is_scope($true) and $true->name ne "null") and (is_scope($false) || is_ifelse_cont($false)) and $self->{'expand'} < 7) { $cond = $self->deparse($cond, 8); $true = $self->deparse($true, 6); $false = $self->deparse($false, 8); return $self->maybe_parens("$cond ? $true : $false", $cx, 8); } $cond = $self->deparse($cond, 1); $true = $self->deparse($true, 0); my $head = $self->keyword("if") . " ($cond) {\n\t$true\n\b}"; my @elsifs; my $elsif; while (!null($false) and is_ifelse_cont($false)) { my $newop = $false->first; my $newcond = $newop->first; my $newtrue = $newcond->sibling; $false = $newtrue->sibling; # last in chain is OP_AND => no else if ($newcond->name eq "lineseq") { # lineseq to ensure correct line numbers in elsif() # Bug #37302 fixed by change #33710. $newcond = $newcond->first->sibling; } $newcond = $self->deparse($newcond, 1); $newtrue = $self->deparse($newtrue, 0); $elsif ||= $self->keyword("elsif"); push @elsifs, "$elsif ($newcond) {\n\t$newtrue\n\b}"; } if (!null($false)) { $false = $cuddle . $self->keyword("else") . " {\n\t" . $self->deparse($false, 0) . "\n\b}\cK"; } else { $false = "\cK"; } return $head . join($cuddle, "", @elsifs) . $false; } sub pp_once { my ($self, $op, $cx) = @_; my $cond = $op->first; my $true = $cond->sibling; my $ret = $self->deparse($true, $cx); $ret =~ s/^(\(?)\$/$1 . $self->keyword("state") . ' $'/e; $ret; } sub loop_common { my $self = shift; my($op, $cx, $init) = @_; my $enter = $op->first; my $kid = $enter->sibling; local(@$self{qw'curstash warnings hints hinthash'}) = @$self{qw'curstash warnings hints hinthash'}; my $head = ""; my $bare = 0; my $body; my $cond = undef; my $name; if ($kid->name eq "lineseq") { # bare or infinite loop if ($kid->last->name eq "unstack") { # infinite $head = "while (1) "; # Can't use for(;;) if there's a continue $cond = ""; } else { $bare = 1; } $body = $kid; } elsif ($enter->name eq "enteriter") { # foreach my $ary = $enter->first->sibling; # first was pushmark my $var = $ary->sibling; if ($ary->name eq 'null' and $enter->private & OPpITER_REVERSED) { # "reverse" was optimised away $ary = listop($self, $ary->first->sibling, 1, 'reverse'); } elsif ($enter->flags & OPf_STACKED and not null $ary->first->sibling->sibling) { $ary = $self->deparse($ary->first->sibling, 9) . " .. " . $self->deparse($ary->first->sibling->sibling, 9); } else { $ary = $self->deparse($ary, 1); } if (null $var) { $var = $self->pp_padsv($enter, 1, 1); } elsif ($var->name eq "rv2gv") { $var = $self->pp_rv2sv($var, 1); if ($enter->private & OPpOUR_INTRO) { # our declarations don't have package names $var =~ s/^(.).*::/$1/; $var = "our $var"; } } elsif ($var->name eq "gv") { $var = "\$" . $self->deparse($var, 1); } else { $var = $self->deparse($var, 1); } $body = $kid->first->first->sibling; # skip OP_AND and OP_ITER if (!is_state $body->first and $body->first->name !~ /^(?:stub|leave|scope)$/) { confess unless $var eq '$_'; $body = $body->first; return $self->deparse($body, 2) . " " . $self->keyword("foreach") . " ($ary)"; } $head = "foreach $var ($ary) "; } elsif ($kid->name eq "null") { # while/until $kid = $kid->first; $name = {"and" => "while", "or" => "until"}->{$kid->name}; $cond = $kid->first; $body = $kid->first->sibling; } elsif ($kid->name eq "stub") { # bare and empty return "{;}"; # {} could be a hashref } # If there isn't a continue block, then the next pointer for the loop # will point to the unstack, which is kid's last child, except # in a bare loop, when it will point to the leaveloop. When neither of # these conditions hold, then the second-to-last child is the continue # block (or the last in a bare loop). my $cont_start = $enter->nextop; my $cont; my $precond; my $postcond; if ($$cont_start != $$op && ${$cont_start} != ${$body->last}) { if ($bare) { $cont = $body->last; } else { $cont = $body->first; while (!null($cont->sibling->sibling)) { $cont = $cont->sibling; } } my $state = $body->first; my $cuddle = $self->{'cuddle'}; my @states; for (; $$state != $$cont; $state = $state->sibling) { push @states, $state; } $body = $self->lineseq(undef, 0, @states); if (defined $cond and not is_scope $cont and $self->{'expand'} < 3) { $precond = "for ($init; "; $postcond = "; " . $self->deparse($cont, 1) .") "; $cont = "\cK"; } else { $cont = $cuddle . "continue {\n\t" . $self->deparse($cont, 0) . "\n\b}\cK"; } } else { return "" if !defined $body; if (length $init) { $precond = "for ($init; "; $postcond = ";) "; } $cont = "\cK"; $body = $self->deparse($body, 0); } if ($precond) { # for(;;) $cond &&= $name eq 'until' ? listop($self, undef, 1, "not", $cond->first) : $self->deparse($cond, 1); $head = "$precond$cond$postcond"; } if ($name && !$head) { ref $cond and $cond = $self->deparse($cond, 1); $head = "$name ($cond) "; } $head =~ s/^(for(?:each)?|while|until)/$self->keyword($1)/e; $body =~ s/;?$/;\n/; return $head . "{\n\t" . $body . "\b}" . $cont; } sub pp_leaveloop { shift->loop_common(@_, "") } sub for_loop { my $self = shift; my($op, $cx) = @_; my $init = $self->deparse($op, 1); my $s = $op->sibling; my $ll = $s->name eq "unstack" ? $s->sibling : $s->first->sibling; return $self->loop_common($ll, $cx, $init); } sub pp_leavetry { my $self = shift; return "eval {\n\t" . $self->pp_leave(@_) . "\n\b}"; } sub _op_is_or_was { my ($op, $expect_type) = @_; my $type = $op->type; return($type == $expect_type || ($type == OP_NULL && $op->targ == $expect_type)); } sub pp_null { my($self, $op, $cx) = @_; # might be 'my $s :Foo(bar);' if ($op->targ == OP_LIST) { my $my_attr = maybe_var_attr($self, $op, $cx); return $my_attr if defined $my_attr; } if (class($op) eq "OP") { # old value is lost return $self->{'ex_const'} if $op->targ == OP_CONST; } elsif (class ($op) eq "COP") { return &pp_nextstate; } elsif ($op->first->name eq 'pushmark' or $op->first->name eq 'null' && $op->first->targ == OP_PUSHMARK && _op_is_or_was($op, OP_LIST)) { return $self->pp_list($op, $cx); } elsif ($op->first->name eq "enter") { return $self->pp_leave($op, $cx); } elsif ($op->first->name eq "leave") { return $self->pp_leave($op->first, $cx); } elsif ($op->first->name eq "scope") { return $self->pp_scope($op->first, $cx); } elsif ($op->targ == OP_STRINGIFY) { return $self->dquote($op, $cx); } elsif ($op->targ == OP_GLOB) { return $self->pp_glob( $op->first # entersub ->first # ex-list ->first # pushmark ->sibling, # glob $cx ); } elsif (!null($op->first->sibling) and $op->first->sibling->name eq "readline" and $op->first->sibling->flags & OPf_STACKED) { return $self->maybe_parens($self->deparse($op->first, 7) . " = " . $self->deparse($op->first->sibling, 7), $cx, 7); } elsif (!null($op->first->sibling) and $op->first->sibling->name =~ /^transr?\z/ and $op->first->sibling->flags & OPf_STACKED) { return $self->maybe_parens($self->deparse($op->first, 20) . " =~ " . $self->deparse($op->first->sibling, 20), $cx, 20); } elsif ($op->flags & OPf_SPECIAL && $cx < 1 && !$op->targ) { return ($self->lex_in_scope("&do") ? "CORE::do" : "do") . " {\n\t". $self->deparse($op->first, $cx) ."\n\b};"; } elsif (!null($op->first->sibling) and $op->first->sibling->name eq "null" and class($op->first->sibling) eq "UNOP" and $op->first->sibling->first->flags & OPf_STACKED and $op->first->sibling->first->name eq "rcatline") { return $self->maybe_parens($self->deparse($op->first, 18) . " .= " . $self->deparse($op->first->sibling, 18), $cx, 18); } else { return $self->deparse($op->first, $cx); } } sub padname { my $self = shift; my $targ = shift; return $self->padname_sv($targ)->PVX; } sub padany { my $self = shift; my $op = shift; return substr($self->padname($op->targ), 1); # skip $/@/% } sub pp_padsv { my $self = shift; my($op, $cx, $forbid_parens) = @_; my $targ = $op->targ; return $self->maybe_my($op, $cx, $self->padname($targ), $self->padname_sv($targ), $forbid_parens); } sub pp_padav { pp_padsv(@_) } # prepend 'keys' where its been optimised away, with suitable handling # of CORE:: and parens sub add_keys_keyword { my ($self, $str, $cx) = @_; $str = $self->maybe_parens($str, $cx, 16); # 'keys %h' versus 'keys(%h)' $str = " $str" unless $str =~ /^\(/; return $self->keyword("keys") . $str; } sub pp_padhv { my ($self, $op, $cx) = @_; my $str = pp_padsv(@_); # with OPpPADHV_ISKEYS the keys op is optimised away, except # in scalar context the old op is kept (but not executed) so its targ # can be used. if ( ($op->private & OPpPADHV_ISKEYS) && !(($op->flags & OPf_WANT) == OPf_WANT_SCALAR)) { $str = $self->add_keys_keyword($str, $cx); } $str; } sub gv_or_padgv { my $self = shift; my $op = shift; if (class($op) eq "PADOP") { return $self->padval($op->padix); } else { # class($op) eq "SVOP" return $op->gv; } } sub pp_gvsv { my $self = shift; my($op, $cx) = @_; my $gv = $self->gv_or_padgv($op); return $self->maybe_local($op, $cx, $self->stash_variable("\$", $self->gv_name($gv), $cx)); } sub pp_gv { my $self = shift; my($op, $cx) = @_; my $gv = $self->gv_or_padgv($op); return $self->maybe_qualify("", $self->gv_name($gv)); } sub pp_aelemfast_lex { my $self = shift; my($op, $cx) = @_; my $name = $self->padname($op->targ); $name =~ s/^@/\$/; my $i = $op->private; $i -= 256 if $i > 127; return $name . "[" . ($i + $self->{'arybase'}) . "]"; } sub pp_aelemfast { my $self = shift; my($op, $cx) = @_; # optimised PADAV, pre 5.15 return $self->pp_aelemfast_lex(@_) if ($op->flags & OPf_SPECIAL); my $gv = $self->gv_or_padgv($op); my($name,$quoted) = $self->stash_variable_name('@',$gv); $name = $quoted ? "$name->" : '$' . $name; my $i = $op->private; $i -= 256 if $i > 127; return $name . "[" . ($i + $self->{'arybase'}) . "]"; } sub rv2x { my $self = shift; my($op, $cx, $type) = @_; if (class($op) eq 'NULL' || !$op->can("first")) { carp("Unexpected op in pp_rv2x"); return 'XXX'; } my $kid = $op->first; if ($kid->name eq "gv") { return $self->stash_variable($type, $self->gv_name($self->gv_or_padgv($kid)), $cx); } elsif (is_scalar $kid) { my $str = $self->deparse($kid, 0); if ($str =~ /^\$([^\w\d])\z/) { # "$$+" isn't a legal way to write the scalar dereference # of $+, since the lexer can't tell you aren't trying to # do something like "$$ + 1" to get one more than your # PID. Either "${$+}" or "$${+}" are workable # disambiguations, but if the programmer did the former, # they'd be in the "else" clause below rather than here. # It's not clear if this should somehow be unified with # the code in dq and re_dq that also adds lexer # disambiguation braces. $str = '$' . "{$1}"; #' } return $type . $str; } else { return $type . "{" . $self->deparse($kid, 0) . "}"; } } sub pp_rv2sv { maybe_local(@_, rv2x(@_, "\$")) } sub pp_rv2gv { maybe_local(@_, rv2x(@_, "*")) } sub pp_rv2hv { my ($self, $op, $cx) = @_; my $str = rv2x(@_, "%"); if ($op->private & OPpRV2HV_ISKEYS) { $str = $self->add_keys_keyword($str, $cx); } return maybe_local(@_, $str); } # skip rv2av sub pp_av2arylen { my $self = shift; my($op, $cx) = @_; my $kid = $op->first; if ($kid->name eq "padav") { return $self->maybe_local($op, $cx, '$#' . $self->padany($kid)); } else { my $kkid; if ( $kid->name eq "rv2av" && ($kkid = $kid->first) && $kkid->name !~ /^(scope|leave|gv)$/) { # handle (expr)->$#* postfix form my $expr; $expr = $self->deparse($kkid, 24); # 24 is '->' $expr = "$expr->\$#*"; # XXX maybe_local is probably wrong here: local($#-expression) # doesn't "do" local (the is no INTRO flag set) return $self->maybe_local($op, $cx, $expr); } else { # handle $#{expr} form # XXX see maybe_local comment above return $self->maybe_local($op, $cx, $self->rv2x($kid, $cx, '$#')); } } } # skip down to the old, ex-rv2cv sub pp_rv2cv { my ($self, $op, $cx) = @_; if (!null($op->first) && $op->first->name eq 'null' && $op->first->targ == OP_LIST) { return $self->rv2x($op->first->first->sibling, $cx, "&") } else { return $self->rv2x($op, $cx, "") } } sub list_const { my $self = shift; my($cx, @list) = @_; my @a = map $self->const($_, 6), @list; if (@a == 0) { return "()"; } elsif (@a == 1) { return $a[0]; } elsif ( @a > 2 and !grep(!/^-?\d+$/, @a)) { # collapse (-1,0,1,2) into (-1..2) my ($s, $e) = @a[0,-1]; my $i = $s; return $self->maybe_parens("$s..$e", $cx, 9) unless grep $i++ != $_, @a; } return $self->maybe_parens(join(", ", @a), $cx, 6); } sub pp_rv2av { my $self = shift; my($op, $cx) = @_; my $kid = $op->first; if ($kid->name eq "const") { # constant list my $av = $self->const_sv($kid); return $self->list_const($cx, $av->ARRAY); } else { return $self->maybe_local($op, $cx, $self->rv2x($op, $cx, "\@")); } } sub is_subscriptable { my $op = shift; if ($op->name =~ /^([ahg]elem|multideref$)/) { return 1; } elsif ($op->name eq "entersub") { my $kid = $op->first; return 0 unless null $kid->sibling; $kid = $kid->first; $kid = $kid->sibling until null $kid->sibling; return 0 if is_scope($kid); $kid = $kid->first; return 0 if $kid->name eq "gv" || $kid->name eq "padcv"; return 0 if is_scalar($kid); return is_subscriptable($kid); } else { return 0; } } sub elem_or_slice_array_name { my $self = shift; my ($array, $left, $padname, $allow_arrow) = @_; if ($array->name eq $padname) { return $self->padany($array); } elsif (is_scope($array)) { # ${expr}[0] return "{" . $self->deparse($array, 0) . "}"; } elsif ($array->name eq "gv") { ($array, my $quoted) = $self->stash_variable_name( $left eq '[' ? '@' : '%', $self->gv_or_padgv($array) ); if (!$allow_arrow && $quoted) { # This cannot happen. die "Invalid variable name $array for slice"; } return $quoted ? "$array->" : $array; } elsif (!$allow_arrow || is_scalar $array) { # $x[0], $$x[0], ... return $self->deparse($array, 24); } else { return undef; } } sub elem_or_slice_single_index { my $self = shift; my ($idx) = @_; $idx = $self->deparse($idx, 1); # Outer parens in an array index will confuse perl # if we're interpolating in a regular expression, i.e. # /$x$foo[(-1)]/ is *not* the same as /$x$foo[-1]/ # # If $self->{parens}, then an initial '(' will # definitely be paired with a final ')'. If # !$self->{parens}, the misleading parens won't # have been added in the first place. # # [You might think that we could get "(...)...(...)" # where the initial and final parens do not match # each other. But we can't, because the above would # only happen if there's an infix binop between the # two pairs of parens, and *that* means that the whole # expression would be parenthesized as well.] # $idx =~ s/^\((.*)\)$/$1/ if $self->{'parens'}; # Hash-element braces will autoquote a bareword inside themselves. # We need to make sure that C<$hash{warn()}> doesn't come out as # C<$hash{warn}>, which has a quite different meaning. Currently # B::Deparse will always quote strings, even if the string was a # bareword in the original (i.e. the OPpCONST_BARE flag is ignored # for constant strings.) So we can cheat slightly here - if we see # a bareword, we know that it is supposed to be a function call. # $idx =~ s/^([A-Za-z_]\w*)$/$1()/; return $idx; } sub elem { my $self = shift; my ($op, $cx, $left, $right, $padname) = @_; my($array, $idx) = ($op->first, $op->first->sibling); $idx = $self->elem_or_slice_single_index($idx); unless ($array->name eq $padname) { # Maybe this has been fixed $array = $array->first; # skip rv2av (or ex-rv2av in _53+) } if (my $array_name=$self->elem_or_slice_array_name ($array, $left, $padname, 1)) { return ($array_name =~ /->\z/ ? $array_name : $array_name eq '#' ? '${#}' : "\$" . $array_name) . $left . $idx . $right; } else { # $x[20][3]{hi} or expr->[20] my $arrow = is_subscriptable($array) ? "" : "->"; return $self->deparse($array, 24) . $arrow . $left . $idx . $right; } } # a simplified version of elem_or_slice_array_name() # for the use of pp_multideref sub multideref_var_name { my $self = shift; my ($gv, $is_hash) = @_; my ($name, $quoted) = $self->stash_variable_name( $is_hash ? '%' : '@', $gv); return $quoted ? "$name->" : $name eq '#' ? '${#}' # avoid ${#}[1] => $#[1] : '$' . $name; } # deparse an OP_MULTICONCAT. If $in_dq is 1, we're within # a double-quoted string, so for example. # "abc\Qdef$x\Ebar" # might get compiled as # multiconcat("abc", metaquote(multiconcat("def", $x)), "bar") # and the inner multiconcat should be deparsed as C rather than # the normal C # Ditto if $in_dq is 2, handle qr/...\Qdef$x\E.../. sub do_multiconcat { my $self = shift; my($op, $cx, $in_dq) = @_; my $kid; my @kids; my $assign; my $append; my $lhs = ""; for ($kid = $op->first; !null $kid; $kid = $kid->sibling) { # skip the consts and/or padsv we've optimised away push @kids, $kid unless $kid->type == OP_NULL && ( $kid->targ == OP_PADSV || $kid->targ == OP_CONST || $kid->targ == OP_PUSHMARK); } $append = ($op->private & OPpMULTICONCAT_APPEND); if ($op->private & OPpTARGET_MY) { # '$lex = ...' or '$lex .= ....' or 'my $lex = ' $lhs = $self->padname($op->targ); $lhs = "my $lhs" if ($op->private & OPpLVAL_INTRO); $assign = 1; } elsif ($op->flags & OPf_STACKED) { # 'expr = ...' or 'expr .= ....' my $expr = $append ? shift(@kids) : pop(@kids); $lhs = $self->deparse($expr, 7); $assign = 1; } if ($assign) { $lhs .= $append ? ' .= ' : ' = '; } my ($nargs, $const_str, @const_lens) = $op->aux_list($self->{curcv}); my @consts; my $i = 0; for (@const_lens) { if ($_ == -1) { push @consts, undef; } else { push @consts, substr($const_str, $i, $_); my @args; $i += $_; } } my $rhs = ""; if ( $in_dq || (($op->private & OPpMULTICONCAT_STRINGIFY) && !$self->{'unquote'})) { # "foo=$foo bar=$bar " my $not_first; while (@consts) { if ($not_first) { my $s = $self->dq(shift(@kids), 18); # don't deparse "a${$}b" as "a$$b" $s = '${$}' if $s eq '$$'; $rhs = dq_disambiguate($rhs, $s); } $not_first = 1; my $c = shift @consts; if (defined $c) { if ($in_dq == 2) { # in pattern: don't convert newline to '\n' etc etc my $s = re_uninterp(escape_re(re_unback($c))); $rhs = re_dq_disambiguate($rhs, $s) } else { my $s = uninterp(escape_str(unback($c))); $rhs = dq_disambiguate($rhs, $s) } } } return $rhs if $in_dq; $rhs = single_delim("qq", '"', $rhs, $self); } elsif ($op->private & OPpMULTICONCAT_FAKE) { # sprintf("foo=%s bar=%s ", $foo, $bar) my @all; @consts = map { $_ //= ''; s/%/%%/g; $_ } @consts; my $fmt = join '%s', @consts; push @all, $self->quoted_const_str($fmt); # the following is a stripped down copy of sub listop {} my $parens = $assign || ($cx >= 5) || $self->{'parens'}; my $fullname = $self->keyword('sprintf'); push @all, map $self->deparse($_, 6), @kids; $rhs = $parens ? "$fullname(" . join(", ", @all) . ")" : "$fullname " . join(", ", @all); } else { # "foo=" . $foo . " bar=" . $bar my @all; my $not_first; while (@consts) { push @all, $self->deparse(shift(@kids), 18) if $not_first; $not_first = 1; my $c = shift @consts; if (defined $c) { push @all, $self->quoted_const_str($c); } } $rhs .= join ' . ', @all; } my $text = $lhs . $rhs; $text = "($text)" if ($cx >= (($assign) ? 7 : 18+1)) || $self->{'parens'}; return $text; } sub pp_multiconcat { my $self = shift; $self->do_multiconcat(@_, 0); } sub pp_multideref { my $self = shift; my($op, $cx) = @_; my $text = ""; if ($op->private & OPpMULTIDEREF_EXISTS) { $text = $self->keyword("exists"). " "; } elsif ($op->private & OPpMULTIDEREF_DELETE) { $text = $self->keyword("delete"). " "; } elsif ($op->private & OPpLVAL_INTRO) { $text = $self->keyword("local"). " "; } if ($op->first && ($op->first->flags & OPf_KIDS)) { # arbitrary initial expression, e.g. f(1,2,3)->[...] my $expr = $self->deparse($op->first, 24); # stop "exists (expr)->{...}" being interpreted as #"(exists (expr))->{...}" $expr = "+$expr" if $expr =~ /^\(/; $text .= $expr; } my @items = $op->aux_list($self->{curcv}); my $actions = shift @items; my $is_hash; my $derefs = 0; while (1) { if (($actions & MDEREF_ACTION_MASK) == MDEREF_reload) { $actions = shift @items; next; } $is_hash = ( ($actions & MDEREF_ACTION_MASK) == MDEREF_HV_pop_rv2hv_helem || ($actions & MDEREF_ACTION_MASK) == MDEREF_HV_gvsv_vivify_rv2hv_helem || ($actions & MDEREF_ACTION_MASK) == MDEREF_HV_padsv_vivify_rv2hv_helem || ($actions & MDEREF_ACTION_MASK) == MDEREF_HV_vivify_rv2hv_helem || ($actions & MDEREF_ACTION_MASK) == MDEREF_HV_padhv_helem || ($actions & MDEREF_ACTION_MASK) == MDEREF_HV_gvhv_helem ); if ( ($actions & MDEREF_ACTION_MASK) == MDEREF_AV_padav_aelem || ($actions & MDEREF_ACTION_MASK) == MDEREF_HV_padhv_helem) { $derefs = 1; $text .= '$' . substr($self->padname(shift @items), 1); } elsif ( ($actions & MDEREF_ACTION_MASK) == MDEREF_AV_gvav_aelem || ($actions & MDEREF_ACTION_MASK) == MDEREF_HV_gvhv_helem) { $derefs = 1; $text .= $self->multideref_var_name(shift @items, $is_hash); } else { if ( ($actions & MDEREF_ACTION_MASK) == MDEREF_AV_padsv_vivify_rv2av_aelem || ($actions & MDEREF_ACTION_MASK) == MDEREF_HV_padsv_vivify_rv2hv_helem) { $text .= $self->padname(shift @items); } elsif ( ($actions & MDEREF_ACTION_MASK) == MDEREF_AV_gvsv_vivify_rv2av_aelem || ($actions & MDEREF_ACTION_MASK) == MDEREF_HV_gvsv_vivify_rv2hv_helem) { $text .= $self->multideref_var_name(shift @items, $is_hash); } elsif ( ($actions & MDEREF_ACTION_MASK) == MDEREF_AV_pop_rv2av_aelem || ($actions & MDEREF_ACTION_MASK) == MDEREF_HV_pop_rv2hv_helem) { if ( ($op->flags & OPf_KIDS) && ( _op_is_or_was($op->first, OP_RV2AV) || _op_is_or_was($op->first, OP_RV2HV)) && ($op->first->flags & OPf_KIDS) && ( _op_is_or_was($op->first->first, OP_AELEM) || _op_is_or_was($op->first->first, OP_HELEM)) ) { $derefs++; } } $text .= '->' if !$derefs++; } if (($actions & MDEREF_INDEX_MASK) == MDEREF_INDEX_none) { last; } $text .= $is_hash ? '{' : '['; if (($actions & MDEREF_INDEX_MASK) == MDEREF_INDEX_const) { my $key = shift @items; if ($is_hash) { $text .= $self->const($key, $cx); } else { $text .= $key; } } elsif (($actions & MDEREF_INDEX_MASK) == MDEREF_INDEX_padsv) { $text .= $self->padname(shift @items); } elsif (($actions & MDEREF_INDEX_MASK) == MDEREF_INDEX_gvsv) { $text .= '$' . ($self->stash_variable_name('$', shift @items))[0]; } $text .= $is_hash ? '}' : ']'; if ($actions & MDEREF_FLAG_last) { last; } $actions >>= MDEREF_SHIFT; } return $text; } sub pp_aelem { maybe_local(@_, elem(@_, "[", "]", "padav")) } sub pp_helem { maybe_local(@_, elem(@_, "{", "}", "padhv")) } sub pp_gelem { my $self = shift; my($op, $cx) = @_; my($glob, $part) = ($op->first, $op->last); $glob = $glob->first; # skip rv2gv $glob = $glob->first if $glob->name eq "rv2gv"; # this one's a bug my $scope = is_scope($glob); $glob = $self->deparse($glob, 0); $part = $self->deparse($part, 1); $glob =~ s/::\z// unless $scope; return "*" . ($scope ? "{$glob}" : $glob) . "{$part}"; } sub slice { my $self = shift; my ($op, $cx, $left, $right, $regname, $padname) = @_; my $last; my(@elems, $kid, $array, $list); if (class($op) eq "LISTOP") { $last = $op->last; } else { # ex-hslice inside delete() for ($kid = $op->first; !null $kid->sibling; $kid = $kid->sibling) {} $last = $kid; } $array = $last; $array = $array->first if $array->name eq $regname or $array->name eq "null"; $array = $self->elem_or_slice_array_name($array,$left,$padname,0); $kid = $op->first->sibling; # skip pushmark if ($kid->name eq "list") { $kid = $kid->first->sibling; # skip list, pushmark for (; !null $kid; $kid = $kid->sibling) { push @elems, $self->deparse($kid, 6); } $list = join(", ", @elems); } else { $list = $self->elem_or_slice_single_index($kid); } my $lead = ( _op_is_or_was($op, OP_KVHSLICE) || _op_is_or_was($op, OP_KVASLICE)) ? '%' : '@'; return $lead . $array . $left . $list . $right; } sub pp_aslice { maybe_local(@_, slice(@_, "[", "]", "rv2av", "padav")) } sub pp_kvaslice { slice(@_, "[", "]", "rv2av", "padav") } sub pp_hslice { maybe_local(@_, slice(@_, "{", "}", "rv2hv", "padhv")) } sub pp_kvhslice { slice(@_, "{", "}", "rv2hv", "padhv") } sub pp_lslice { my $self = shift; my($op, $cx) = @_; my $idx = $op->first; my $list = $op->last; my(@elems, $kid); $list = $self->deparse($list, 1); $idx = $self->deparse($idx, 1); return "($list)" . "[$idx]"; } sub want_scalar { my $op = shift; return ($op->flags & OPf_WANT) == OPf_WANT_SCALAR; } sub want_list { my $op = shift; return ($op->flags & OPf_WANT) == OPf_WANT_LIST; } sub _method { my $self = shift; my($op, $cx) = @_; my $kid = $op->first->sibling; # skip pushmark my($meth, $obj, @exprs); if ($kid->name eq "list" and want_list $kid) { # When an indirect object isn't a bareword but the args are in # parens, the parens aren't part of the method syntax (the LLAFR # doesn't apply), but they make a list with OPf_PARENS set that # doesn't get flattened by the append_elem that adds the method, # making a (object, arg1, arg2, ...) list where the object # usually is. This can be distinguished from # '($obj, $arg1, $arg2)->meth()' (which is legal if $arg2 is an # object) because in the later the list is in scalar context # as the left side of -> always is, while in the former # the list is in list context as method arguments always are. # (Good thing there aren't method prototypes!) $meth = $kid->sibling; $kid = $kid->first->sibling; # skip pushmark $obj = $kid; $kid = $kid->sibling; for (; not null $kid; $kid = $kid->sibling) { push @exprs, $kid; } } else { $obj = $kid; $kid = $kid->sibling; for (; !null ($kid->sibling) && $kid->name!~/^method(?:_named)?\z/; $kid = $kid->sibling) { push @exprs, $kid } $meth = $kid; } if ($meth->name eq "method_named") { $meth = $self->meth_sv($meth)->PV; } elsif ($meth->name eq "method_super") { $meth = "SUPER::".$self->meth_sv($meth)->PV; } elsif ($meth->name eq "method_redir") { $meth = $self->meth_rclass_sv($meth)->PV.'::'.$self->meth_sv($meth)->PV; } elsif ($meth->name eq "method_redir_super") { $meth = $self->meth_rclass_sv($meth)->PV.'::SUPER::'. $self->meth_sv($meth)->PV; } else { $meth = $meth->first; if ($meth->name eq "const") { # As of 5.005_58, this case is probably obsoleted by the # method_named case above $meth = $self->const_sv($meth)->PV; # needs to be bare } } return { method => $meth, variable_method => ref($meth), object => $obj, args => \@exprs }, $cx; } # compat function only sub method { my $self = shift; my $info = $self->_method(@_); return $self->e_method( $self->_method(@_) ); } sub e_method { my ($self, $info, $cx) = @_; my $obj = $self->deparse($info->{object}, 24); my $meth = $info->{method}; $meth = $self->deparse($meth, 1) if $info->{variable_method}; my $args = join(", ", map { $self->deparse($_, 6) } @{$info->{args}} ); if ($info->{object}->name eq 'scope' && want_list $info->{object}) { # method { $object } # This must be deparsed this way to preserve list context # of $object. my $need_paren = $cx >= 6; return '(' x $need_paren . $meth . substr($obj,2) # chop off the "do" . " $args" . ')' x $need_paren; } my $kid = $obj . "->" . $meth; if (length $args) { return $kid . "(" . $args . ")"; # parens mandatory } else { return $kid; } } # returns "&" if the prototype doesn't match the args, # or ("", $args_after_prototype_demunging) if it does. sub check_proto { my $self = shift; return "&" if $self->{'noproto'}; my($proto, @args) = @_; my($arg, $real); my $doneok = 0; my @reals; # An unbackslashed @ or % gobbles up the rest of the args 1 while $proto =~ s/(?deparse($_, 6), @args); @args = (); } else { $arg = shift @args; last unless $arg; if ($chr eq "\$" || $chr eq "_") { if (want_scalar $arg) { push @reals, $self->deparse($arg, 6); } else { return "&"; } } elsif ($chr eq "&") { if ($arg->name =~ /^(s?refgen|undef)$/) { push @reals, $self->deparse($arg, 6); } else { return "&"; } } elsif ($chr eq "*") { if ($arg->name =~ /^s?refgen$/ and $arg->first->first->name eq "rv2gv") { $real = $arg->first->first; # skip refgen, null if ($real->first->name eq "gv") { push @reals, $self->deparse($real, 6); } else { push @reals, $self->deparse($real->first, 6); } } else { return "&"; } } elsif (substr($chr, 0, 1) eq "\\") { $chr =~ tr/\\[]//d; if ($arg->name =~ /^s?refgen$/ and !null($real = $arg->first) and ($chr =~ /\$/ && is_scalar($real->first) or ($chr =~ /@/ && class($real->first->sibling) ne 'NULL' && $real->first->sibling->name =~ /^(rv2|pad)av$/) or ($chr =~ /%/ && class($real->first->sibling) ne 'NULL' && $real->first->sibling->name =~ /^(rv2|pad)hv$/) #or ($chr =~ /&/ # This doesn't work # && $real->first->name eq "rv2cv") or ($chr =~ /\*/ && $real->first->name eq "rv2gv"))) { push @reals, $self->deparse($real, 6); } else { return "&"; } } } } return "&" if $proto and !$doneok; # too few args and no ';' return "&" if @args; # too many args return ("", join ", ", @reals); } sub retscalar { my $name = $_[0]->name; # XXX There has to be a better way of doing this scalar-op check. # Currently PL_opargs is not exposed. if ($name eq 'null') { $name = substr B::ppname($_[0]->targ), 3 } $name =~ /^(?:scalar|pushmark|wantarray|const|gvsv|gv|padsv|rv2gv |rv2sv|av2arylen|anoncode|prototype|srefgen|ref|bless |regcmaybe|regcreset|regcomp|qr|subst|substcont|trans |transr|sassign|chop|schop|chomp|schomp|defined|undef |study|pos|preinc|i_preinc|predec|i_predec|postinc |i_postinc|postdec|i_postdec|pow|multiply|i_multiply |divide|i_divide|modulo|i_modulo|add|i_add|subtract |i_subtract|concat|multiconcat|stringify|left_shift|right_shift|lt |i_lt|gt|i_gt|le|i_le|ge|i_ge|eq|i_eq|ne|i_ne|ncmp|i_ncmp |slt|sgt|sle|sge|seq|sne|scmp|[sn]?bit_(?:and|x?or)|negate |i_negate|not|[sn]?complement|smartmatch|atan2|sin|cos |rand|srand|exp|log|sqrt|int|hex|oct|abs|length|substr |vec|index|rindex|sprintf|formline|ord|chr|crypt|ucfirst |lcfirst|uc|lc|quotemeta|aelemfast|aelem|exists|helem |pack|join|anonlist|anonhash|push|pop|shift|unshift|xor |andassign|orassign|dorassign|warn|die|reset|nextstate |dbstate|unstack|last|next|redo|dump|goto|exit|open|close |pipe_op|fileno|umask|binmode|tie|untie|tied|dbmopen |dbmclose|select|getc|read|enterwrite|prtf|print|say |sysopen|sysseek|sysread|syswrite|eof|tell|seek|truncate |fcntl|ioctl|flock|send|recv|socket|sockpair|bind|connect |listen|accept|shutdown|gsockopt|ssockopt|getsockname |getpeername|ftrread|ftrwrite|ftrexec|fteread|ftewrite |fteexec|ftis|ftsize|ftmtime|ftatime|ftctime|ftrowned |fteowned|ftzero|ftsock|ftchr|ftblk|ftfile|ftdir|ftpipe |ftsuid|ftsgid|ftsvtx|ftlink|fttty|fttext|ftbinary|chdir |chown|chroot|unlink|chmod|utime|rename|link|symlink |readlink|mkdir|rmdir|open_dir|telldir|seekdir|rewinddir |closedir|fork|wait|waitpid|system|exec|kill|getppid |getpgrp|setpgrp|getpriority|setpriority|time|alarm|sleep |shmget|shmctl|shmread|shmwrite|msgget|msgctl|msgsnd |msgrcv|semop|semget|semctl|hintseval|shostent|snetent |sprotoent|sservent|ehostent|enetent|eprotoent|eservent |spwent|epwent|sgrent|egrent|getlogin|syscall|lock|runcv |fc)\z/x } sub pp_entersub { my $self = shift; my($op, $cx) = @_; return $self->e_method($self->_method($op, $cx)) unless null $op->first->sibling; my $prefix = ""; my $amper = ""; my($kid, @exprs); if ($op->flags & OPf_SPECIAL && !($op->flags & OPf_MOD)) { $prefix = "do "; } elsif ($op->private & OPpENTERSUB_AMPER) { $amper = "&"; } $kid = $op->first; $kid = $kid->first->sibling; # skip ex-list, pushmark for (; not null $kid->sibling; $kid = $kid->sibling) { push @exprs, $kid; } my $simple = 0; my $proto = undef; my $lexical; if (is_scope($kid)) { $amper = "&"; $kid = "{" . $self->deparse($kid, 0) . "}"; } elsif ($kid->first->name eq "gv") { my $gv = $self->gv_or_padgv($kid->first); my $cv; if (class($gv) eq 'GV' && class($cv = $gv->CV) ne "SPECIAL" || $gv->FLAGS & SVf_ROK && class($cv = $gv->RV) eq 'CV') { $proto = $cv->PV if $cv->FLAGS & SVf_POK; } $simple = 1; # only calls of named functions can be prototyped $kid = $self->maybe_qualify("!", $self->gv_name($gv)); my $fq; # Fully qualify any sub name that conflicts with a lexical. if ($self->lex_in_scope("&$kid") || $self->lex_in_scope("&$kid", 1)) { $fq++; } elsif (!$amper) { if ($kid eq 'main::') { $kid = '::'; } else { if ($kid !~ /::/ && $kid ne 'x') { # Fully qualify any sub name that is also a keyword. While # we could check the import flag, we cannot guarantee that # the code deparsed so far would set that flag, so we qual- # ify the names regardless of importation. if (exists $feature_keywords{$kid}) { $fq++ if $self->feature_enabled($kid); } elsif (do { local $@; local $SIG{__DIE__}; eval { () = prototype "CORE::$kid"; 1 } }) { $fq++ } } if ($kid !~ /^(?:\w|::)(?:[\w\d]|::(?!\z))*\z/) { $kid = single_delim("q", "'", $kid, $self) . '->'; } } } $fq and substr $kid, 0, 0, = $self->{'curstash'}.'::'; } elsif (is_scalar ($kid->first) && $kid->first->name ne 'rv2cv') { $amper = "&"; $kid = $self->deparse($kid, 24); } else { $prefix = ""; my $grandkid = $kid->first; my $arrow = ($lexical = $grandkid->name eq "padcv") || is_subscriptable($grandkid) ? "" : "->"; $kid = $self->deparse($kid, 24) . $arrow; if ($lexical) { my $padlist = $self->{'curcv'}->PADLIST; my $padoff = $grandkid->targ; my $padname = $padlist->ARRAYelt(0)->ARRAYelt($padoff); my $protocv = $padname->FLAGS & SVpad_STATE ? $padlist->ARRAYelt(1)->ARRAYelt($padoff) : $padname->PROTOCV; if ($protocv->FLAGS & SVf_POK) { $proto = $protocv->PV } $simple = 1; } } # Doesn't matter how many prototypes there are, if # they haven't happened yet! my $declared = $lexical || exists $self->{'subs_declared'}{$kid}; if (not $declared and $self->{'in_coderef2text'}) { no strict 'refs'; no warnings 'uninitialized'; $declared = ( defined &{ ${$self->{'curstash'}."::"}{$kid} } && !exists $self->{'subs_deparsed'}{$self->{'curstash'}."::".$kid} && defined prototype $self->{'curstash'}."::".$kid ); } if (!$declared && defined($proto)) { # Avoid "too early to check prototype" warning ($amper, $proto) = ('&'); } my $args; my $listargs = 1; if ($declared and defined $proto and not $amper) { ($amper, $args) = $self->check_proto($proto, @exprs); $listargs = $amper; } if ($listargs) { $args = join(", ", map( ($_->flags & OPf_WANT) == OPf_WANT_SCALAR && !retscalar($_) ? $self->maybe_parens_unop('scalar', $_, 6) : $self->deparse($_, 6), @exprs )); } if ($prefix or $amper) { if ($kid eq '&') { $kid = "{$kid}" } # &{&} cannot be written as && if ($op->flags & OPf_STACKED) { return $prefix . $amper . $kid . "(" . $args . ")"; } else { return $prefix . $amper. $kid; } } else { # It's a syntax error to call CORE::GLOBAL::foo with a prefix, # so it must have been translated from a keyword call. Translate # it back. $kid =~ s/^CORE::GLOBAL:://; my $dproto = defined($proto) ? $proto : "undefined"; my $scalar_proto = $dproto =~ /^;*(?:[\$*_+]|\\.|\\\[[^]]\])\z/; if (!$declared) { return "$kid(" . $args . ")"; } elsif ($dproto =~ /^\s*\z/) { return $kid; } elsif ($scalar_proto and is_scalar($exprs[0])) { # is_scalar is an excessively conservative test here: # really, we should be comparing to the precedence of the # top operator of $exprs[0] (ala unop()), but that would # take some major code restructuring to do right. return $self->maybe_parens_func($kid, $args, $cx, 16); } elsif (not $scalar_proto and defined($proto) || $simple) { #' return $self->maybe_parens_func($kid, $args, $cx, 5); } else { return "$kid(" . $args . ")"; } } } sub pp_enterwrite { unop(@_, "write") } # escape things that cause interpolation in double quotes, # but not character escapes sub uninterp { my($str) = @_; $str =~ s/(^|\G|[^\\])((?:\\\\)*)([\$\@]|\\[uUlLQE])/$1$2\\$3/g; return $str; } { my $bal; BEGIN { use re "eval"; # Matches any string which is balanced with respect to {braces} $bal = qr( (?: [^\\{}] | \\\\ | \\[{}] | \{(??{$bal})\} )* )x; } # the same, but treat $|, $), $( and $ at the end of the string differently # and leave comments unmangled for the sake of /x and (?x). sub re_uninterp { my($str) = @_; $str =~ s/ ( ^|\G # $1 | [^\\] ) ( # $2 (?:\\\\)* ) ( # $3 ( \(\?\??\{$bal\}\) # $4 (skip over (?{}) and (??{}) blocks) | \#[^\n]* # (skip over comments) ) | [\$\@] (?!\||\)|\(|$|\s) | \\[uUlLQE] ) /defined($4) && length($4) ? "$1$2$4" : "$1$2\\$3"/xeg; return $str; } } # character escapes, but not delimiters that might need to be escaped sub escape_str { # ASCII, UTF8 my($str) = @_; $str =~ s/(.)/ord($1) > 255 ? sprintf("\\x{%x}", ord($1)) : $1/eg; $str =~ s/\a/\\a/g; # $str =~ s/\cH/\\b/g; # \b means something different in a regex; and \cH # isn't a backspace in EBCDIC $str =~ s/\t/\\t/g; $str =~ s/\n/\\n/g; $str =~ s/\e/\\e/g; $str =~ s/\f/\\f/g; $str =~ s/\r/\\r/g; $str =~ s/([\cA-\cZ])/'\\c' . $unctrl{$1}/ge; $str =~ s/([[:^print:]])/sprintf("\\%03o", ord($1))/age; return $str; } # For regexes. Leave whitespace unmangled in case of /x or (?x). sub escape_re { my($str) = @_; $str =~ s/(.)/ord($1) > 255 ? sprintf("\\x{%x}", ord($1)) : $1/eg; $str =~ s/([[:^print:]])/ ($1 =~ y! \t\n!!) ? $1 : sprintf("\\%03o", ord($1))/age; $str =~ s/\n/\n\f/g; return $str; } # Don't do this for regexen sub unback { my($str) = @_; $str =~ s/\\/\\\\/g; return $str; } # Remove backslashes which precede literal control characters, # to avoid creating ambiguity when we escape the latter. # # Don't remove a backslash from escaped whitespace: where the T represents # a literal tab character, /T/x is not equivalent to /\T/x sub re_unback { my($str) = @_; # the insane complexity here is due to the behaviour of "\c\" $str =~ s/ # these two lines ensure that the backslash we're about to # remove isn't preceeded by something which makes it part # of a \c (^ | [^\\] | \\c\\) # $1 (?'], ['{','}']) { ($open, $close) = @$ar; $fail = 0; $cnt = 0; $last_bs = 0; for $c (@str) { if ($c eq $open) { $fail = 1 if $last_bs; $cnt++; } elsif ($c eq $close) { $fail = 1 if $last_bs; $cnt--; if ($cnt < 0) { # qq()() isn't ")(" $fail = 1; last; } } $last_bs = $c eq '\\'; } $fail = 1 if $cnt != 0; return ($open, "$open$str$close") if not $fail; } return ("", $str); } sub single_delim { my($q, $default, $str, $self) = @_; return "$default$str$default" if $default and index($str, $default) == -1; my $coreq = $self->keyword($q); # maybe CORE::q if ($q ne 'qr') { (my $succeed, $str) = balanced_delim($str); return "$coreq$str" if $succeed; } for my $delim ('/', '"', '#') { return "$coreq$delim" . $str . $delim if index($str, $delim) == -1; } if ($default) { $str =~ s/$default/\\$default/g; return "$default$str$default"; } else { $str =~ s[/][\\/]g; return "$coreq/$str/"; } } my $max_prec; BEGIN { $max_prec = int(0.999 + 8*length(pack("F", 42))*log(2)/log(10)); } # Split a floating point number into an integer mantissa and a binary # exponent. Assumes you've already made sure the number isn't zero or # some weird infinity or NaN. sub split_float { my($f) = @_; my $exponent = 0; if ($f == int($f)) { while ($f % 2 == 0) { $f /= 2; $exponent++; } } else { while ($f != int($f)) { $f *= 2; $exponent--; } } my $mantissa = sprintf("%.0f", $f); return ($mantissa, $exponent); } # suitably single- or double-quote a literal constant string sub quoted_const_str { my ($self, $str) =@_; if ($str =~ /[[:^print:]]/a) { return single_delim("qq", '"', uninterp(escape_str unback $str), $self); } else { return single_delim("q", "'", unback($str), $self); } } sub const { my $self = shift; my($sv, $cx) = @_; if ($self->{'use_dumper'}) { return $self->const_dumper($sv, $cx); } if (class($sv) eq "SPECIAL") { # sv_undef, sv_yes, sv_no return $$sv == 3 ? $self->maybe_parens("!1", $cx, 21) : ('undef', '1')[$$sv-1]; } if (class($sv) eq "NULL") { return 'undef'; } # convert a version object into the "v1.2.3" string in its V magic if ($sv->FLAGS & SVs_RMG) { for (my $mg = $sv->MAGIC; $mg; $mg = $mg->MOREMAGIC) { return $mg->PTR if $mg->TYPE eq 'V'; } } if ($sv->FLAGS & SVf_IOK) { my $str = $sv->int_value; $str = $self->maybe_parens($str, $cx, 21) if $str < 0; return $str; } elsif ($sv->FLAGS & SVf_NOK) { my $nv = $sv->NV; if ($nv == 0) { if (pack("F", $nv) eq pack("F", 0)) { # positive zero return "0"; } else { # negative zero return $self->maybe_parens("-.0", $cx, 21); } } elsif (1/$nv == 0) { if ($nv > 0) { # positive infinity return $self->maybe_parens("9**9**9", $cx, 22); } else { # negative infinity return $self->maybe_parens("-9**9**9", $cx, 21); } } elsif ($nv != $nv) { # NaN if (pack("F", $nv) eq pack("F", sin(9**9**9))) { # the normal kind return "sin(9**9**9)"; } elsif (pack("F", $nv) eq pack("F", -sin(9**9**9))) { # the inverted kind return $self->maybe_parens("-sin(9**9**9)", $cx, 21); } else { # some other kind my $hex = unpack("h*", pack("F", $nv)); return qq'unpack("F", pack("h*", "$hex"))'; } } # first, try the default stringification my $str = "$nv"; if ($str != $nv) { # failing that, try using more precision $str = sprintf("%.${max_prec}g", $nv); # if (pack("F", $str) ne pack("F", $nv)) { if ($str != $nv) { # not representable in decimal with whatever sprintf() # and atof() Perl is using here. my($mant, $exp) = split_float($nv); return $self->maybe_parens("$mant * 2**$exp", $cx, 19); } } $str = $self->maybe_parens($str, $cx, 21) if $nv < 0; return $str; } elsif ($sv->FLAGS & SVf_ROK && $sv->can("RV")) { my $ref = $sv->RV; my $class = class($ref); if ($class eq "AV") { return "[" . $self->list_const(2, $ref->ARRAY) . "]"; } elsif ($class eq "HV") { my %hash = $ref->ARRAY; my @elts; for my $k (sort keys %hash) { push @elts, "$k => " . $self->const($hash{$k}, 6); } return "{" . join(", ", @elts) . "}"; } elsif ($class eq "CV") { no overloading; if ($self->{curcv} && $self->{curcv}->object_2svref == $ref->object_2svref) { return $self->keyword("__SUB__"); } return "sub " . $self->deparse_sub($ref); } if ($class ne 'SPECIAL' and $ref->FLAGS & SVs_SMG) { for (my $mg = $ref->MAGIC; $mg; $mg = $mg->MOREMAGIC) { if ($mg->TYPE eq 'r') { my $re = re_uninterp(escape_re(re_unback($mg->precomp))); return single_delim("qr", "", $re, $self); } } } my $const = $self->const($ref, 20); if ($self->{in_subst_repl} && $const =~ /^[0-9]/) { $const = "($const)"; } return $self->maybe_parens("\\$const", $cx, 20); } elsif ($sv->FLAGS & SVf_POK) { my $str = $sv->PV; return $self->quoted_const_str($str); } else { return "undef"; } } sub const_dumper { my $self = shift; my($sv, $cx) = @_; my $ref = $sv->object_2svref(); my $dumper = Data::Dumper->new([$$ref], ['$v']); $dumper->Purity(1)->Terse(1)->Deparse(1)->Indent(0)->Useqq(1)->Sortkeys(1); my $str = $dumper->Dump(); if ($str =~ /^\$v/) { return '${my ' . $str . ' \$v}'; } else { return $str; } } sub const_sv { my $self = shift; my $op = shift; my $sv = $op->sv; # the constant could be in the pad (under useithreads) $sv = $self->padval($op->targ) unless $$sv; return $sv; } sub meth_sv { my $self = shift; my $op = shift; my $sv = $op->meth_sv; # the constant could be in the pad (under useithreads) $sv = $self->padval($op->targ) unless $$sv; return $sv; } sub meth_rclass_sv { my $self = shift; my $op = shift; my $sv = $op->rclass; # the constant could be in the pad (under useithreads) $sv = $self->padval($sv) unless ref $sv; return $sv; } sub pp_const { my $self = shift; my($op, $cx) = @_; if ($op->private & OPpCONST_ARYBASE) { return '$['; } # if ($op->private & OPpCONST_BARE) { # trouble with '=>' autoquoting # return $self->const_sv($op)->PV; # } my $sv = $self->const_sv($op); return $self->const($sv, $cx); } # Join two components of a double-quoted string, disambiguating # "${foo}bar", "${foo}{bar}", "${foo}[1]", "$foo\::bar" sub dq_disambiguate { my ($first, $last) = @_; ($last =~ /^[A-Z\\\^\[\]_?]/ && $first =~ s/([\$@])\^$/${1}{^}/) # "${^}W" etc || ($last =~ /^[:'{\[\w_]/ && #' $first =~ s/([\$@])([A-Za-z_]\w*)$/${1}{$2}/); return $first . $last; } # Deparse a double-quoted optree. For example, "$a[0]\Q$b\Efo\"o" gets # compiled to concat(concat($[0],quotemeta($b)),const("fo\"o")), and this # sub deparses it back to $a[0]\Q$b\Efo"o # (It does not add delimiters) sub dq { my $self = shift; my $op = shift; my $type = $op->name; if ($type eq "const") { return '$[' if $op->private & OPpCONST_ARYBASE; return uninterp(escape_str(unback($self->const_sv($op)->as_string))); } elsif ($type eq "concat") { return dq_disambiguate($self->dq($op->first), $self->dq($op->last)); } elsif ($type eq "multiconcat") { return $self->do_multiconcat($op, 26, 1); } elsif ($type eq "uc") { return '\U' . $self->dq($op->first->sibling) . '\E'; } elsif ($type eq "lc") { return '\L' . $self->dq($op->first->sibling) . '\E'; } elsif ($type eq "ucfirst") { return '\u' . $self->dq($op->first->sibling); } elsif ($type eq "lcfirst") { return '\l' . $self->dq($op->first->sibling); } elsif ($type eq "quotemeta") { return '\Q' . $self->dq($op->first->sibling) . '\E'; } elsif ($type eq "fc") { return '\F' . $self->dq($op->first->sibling) . '\E'; } elsif ($type eq "join") { return $self->deparse($op->last, 26); # was join($", @ary) } else { return $self->deparse($op, 26); } } sub pp_backtick { my $self = shift; my($op, $cx) = @_; # skip pushmark if it exists (readpipe() vs ``) my $child = $op->first->sibling->isa('B::NULL') ? $op->first : $op->first->sibling; if ($self->pure_string($child)) { return single_delim("qx", '`', $self->dq($child, 1), $self); } unop($self, @_, "readpipe"); } sub dquote { my $self = shift; my($op, $cx) = @_; my $kid = $op->first->sibling; # skip ex-stringify, pushmark return $self->deparse($kid, $cx) if $self->{'unquote'}; $self->maybe_targmy($kid, $cx, sub {single_delim("qq", '"', $self->dq($_[1]), $self)}); } # OP_STRINGIFY is a listop, but it only ever has one arg sub pp_stringify { my ($self, $op, $cx) = @_; my $kid = $op->first->sibling; while ($kid->name eq 'null' && !null($kid->first)) { $kid = $kid->first; } if ($kid->name =~ /^(?:const|padsv|rv2sv|av2arylen|gvsv|multideref |aelemfast(?:_lex)?|[ah]elem|join|concat)\z/x) { maybe_targmy(@_, \&dquote); } else { # Actually an optimised join. my $result = listop(@_,"join"); $result =~ s/join([( ])/join$1$self->{'ex_const'}, /; $result; } } # tr/// and s/// (and tr[][], tr[]//, tr###, etc) # note that tr(from)/to/ is OK, but not tr/from/(to) sub double_delim { my($from, $to) = @_; my($succeed, $delim); if ($from !~ m[/] and $to !~ m[/]) { return "/$from/$to/"; } elsif (($succeed, $from) = balanced_delim($from) and $succeed) { if (($succeed, $to) = balanced_delim($to) and $succeed) { return "$from$to"; } else { for $delim ('/', '"', '#') { # note no "'" -- s''' is special return "$from$delim$to$delim" if index($to, $delim) == -1; } $to =~ s[/][\\/]g; return "$from/$to/"; } } else { for $delim ('/', '"', '#') { # note no ' return "$delim$from$delim$to$delim" if index($to . $from, $delim) == -1; } $from =~ s[/][\\/]g; $to =~ s[/][\\/]g; return "/$from/$to/"; } } # Escape a characrter. # Only used by tr///, so backslashes hyphens sub pchr { # ASCII my($n) = @_; if ($n == ord '\\') { return '\\\\'; } elsif ($n == ord "-") { return "\\-"; } elsif (utf8::native_to_unicode($n) >= utf8::native_to_unicode(ord(' ')) and utf8::native_to_unicode($n) <= utf8::native_to_unicode(ord('~'))) { # I'm presuming a regex is not ok here, otherwise we could have used # /[[:print:]]/a to get here return chr($n); } elsif ($n == ord "\a") { return '\\a'; } elsif ($n == ord "\b") { return '\\b'; } elsif ($n == ord "\t") { return '\\t'; } elsif ($n == ord "\n") { return '\\n'; } elsif ($n == ord "\e") { return '\\e'; } elsif ($n == ord "\f") { return '\\f'; } elsif ($n == ord "\r") { return '\\r'; } elsif ($n >= ord("\cA") and $n <= ord("\cZ")) { return '\\c' . $unctrl{chr $n}; } else { # return '\x' . sprintf("%02x", $n); return '\\' . sprintf("%03o", $n); } } # Convert a list of characters into a string suitable for tr/// search or # replacement, with suitable escaping and collapsing of ranges sub collapse { my(@chars) = @_; my($str, $c, $tr) = (""); for ($c = 0; $c < @chars; $c++) { $tr = $chars[$c]; $str .= pchr($tr); if ($c <= $#chars - 2 and $chars[$c + 1] == $tr + 1 and $chars[$c + 2] == $tr + 2) { for (; $c <= $#chars-1 and $chars[$c + 1] == $chars[$c] + 1; $c++) {} $str .= "-"; $str .= pchr($chars[$c]); } } return $str; } sub tr_decode_byte { my($table, $flags) = @_; my $ssize_t = $Config{ptrsize} == 8 ? 'q' : 'l'; my ($size, @table) = unpack("${ssize_t}s*", $table); pop @table; # remove the wildcard final entry my($c, $tr, @from, @to, @delfrom, $delhyphen); if ($table[ord "-"] != -1 and $table[ord("-") - 1] == -1 || $table[ord("-") + 1] == -1) { $tr = $table[ord "-"]; $table[ord "-"] = -1; if ($tr >= 0) { @from = ord("-"); @to = $tr; } else { # -2 ==> delete $delhyphen = 1; } } for ($c = 0; $c < @table; $c++) { $tr = $table[$c]; if ($tr >= 0) { push @from, $c; push @to, $tr; } elsif ($tr == -2) { push @delfrom, $c; } } @from = (@from, @delfrom); if ($flags & OPpTRANS_COMPLEMENT) { unless ($flags & OPpTRANS_DELETE) { @to = () if ("@from" eq "@to"); } my @newfrom = (); my %from; @from{@from} = (1) x @from; for ($c = 0; $c < 256; $c++) { push @newfrom, $c unless $from{$c}; } @from = @newfrom; } unless ($flags & OPpTRANS_DELETE || !@to) { pop @to while $#to and $to[$#to] == $to[$#to -1]; } my($from, $to); $from = collapse(@from); $to = collapse(@to); $from .= "-" if $delhyphen; return ($from, $to); } sub tr_chr { my $x = shift; if ($x == ord "-") { return "\\-"; } elsif ($x == ord "\\") { return "\\\\"; } else { return chr $x; } } # XXX This doesn't yet handle all cases correctly either sub tr_decode_utf8 { my($swash_hv, $flags) = @_; my %swash = $swash_hv->ARRAY; my $final = undef; $final = $swash{'FINAL'}->IV if exists $swash{'FINAL'}; my $none = $swash{"NONE"}->IV; my $extra = $none + 1; my(@from, @delfrom, @to); my $line; foreach $line (split /\n/, $swash{'LIST'}->PV) { my($min, $max, $result) = split(/\t/, $line); $min = hex $min; if (length $max) { $max = hex $max; } else { $max = $min; } $result = hex $result; if ($result == $extra) { push @delfrom, [$min, $max]; } else { push @from, [$min, $max]; push @to, [$result, $result + $max - $min]; } } for my $i (0 .. $#from) { if ($from[$i][0] == ord '-') { unshift @from, splice(@from, $i, 1); unshift @to, splice(@to, $i, 1); last; } elsif ($from[$i][1] == ord '-') { $from[$i][1]--; $to[$i][1]--; unshift @from, ord '-'; unshift @to, ord '-'; last; } } for my $i (0 .. $#delfrom) { if ($delfrom[$i][0] == ord '-') { push @delfrom, splice(@delfrom, $i, 1); last; } elsif ($delfrom[$i][1] == ord '-') { $delfrom[$i][1]--; push @delfrom, ord '-'; last; } } if (defined $final and $to[$#to][1] != $final) { push @to, [$final, $final]; } push @from, @delfrom; if ($flags & OPpTRANS_COMPLEMENT) { my @newfrom; my $next = 0; for my $i (0 .. $#from) { push @newfrom, [$next, $from[$i][0] - 1]; $next = $from[$i][1] + 1; } @from = (); for my $range (@newfrom) { if ($range->[0] <= $range->[1]) { push @from, $range; } } } my($from, $to, $diff); for my $chunk (@from) { $diff = $chunk->[1] - $chunk->[0]; if ($diff > 1) { $from .= tr_chr($chunk->[0]) . "-" . tr_chr($chunk->[1]); } elsif ($diff == 1) { $from .= tr_chr($chunk->[0]) . tr_chr($chunk->[1]); } else { $from .= tr_chr($chunk->[0]); } } for my $chunk (@to) { $diff = $chunk->[1] - $chunk->[0]; if ($diff > 1) { $to .= tr_chr($chunk->[0]) . "-" . tr_chr($chunk->[1]); } elsif ($diff == 1) { $to .= tr_chr($chunk->[0]) . tr_chr($chunk->[1]); } else { $to .= tr_chr($chunk->[0]); } } #$final = sprintf("%04x", $final) if defined $final; #$none = sprintf("%04x", $none) if defined $none; #$extra = sprintf("%04x", $extra) if defined $extra; #print STDERR "final: $final\n none: $none\nextra: $extra\n"; #print STDERR $swash{'LIST'}->PV; return (escape_str($from), escape_str($to)); } sub pp_trans { my $self = shift; my($op, $cx, $morflags) = @_; my($from, $to); my $class = class($op); my $priv_flags = $op->private; if ($class eq "PVOP") { ($from, $to) = tr_decode_byte($op->pv, $priv_flags); } elsif ($class eq "PADOP") { ($from, $to) = tr_decode_utf8($self->padval($op->padix)->RV, $priv_flags); } else { # class($op) eq "SVOP" ($from, $to) = tr_decode_utf8($op->sv->RV, $priv_flags); } my $flags = ""; $flags .= "c" if $priv_flags & OPpTRANS_COMPLEMENT; $flags .= "d" if $priv_flags & OPpTRANS_DELETE; $to = "" if $from eq $to and $flags eq ""; $flags .= "s" if $priv_flags & OPpTRANS_SQUASH; $flags .= $morflags if defined $morflags; my $ret = $self->keyword("tr") . double_delim($from, $to) . $flags; if (my $targ = $op->targ) { return $self->maybe_parens($self->padname($targ) . " =~ $ret", $cx, 20); } return $ret; } sub pp_transr { push @_, 'r'; goto &pp_trans } # Join two components of a double-quoted re, disambiguating # "${foo}bar", "${foo}{bar}", "${foo}[1]". sub re_dq_disambiguate { my ($first, $last) = @_; ($last =~ /^[A-Z\\\^\[\]_?]/ && $first =~ s/([\$@])\^$/${1}{^}/) # "${^}W" etc || ($last =~ /^[{\[\w_]/ && $first =~ s/([\$@])([A-Za-z_]\w*)$/${1}{$2}/); return $first . $last; } # Like dq(), but different sub re_dq { my $self = shift; my ($op) = @_; my $type = $op->name; if ($type eq "const") { return '$[' if $op->private & OPpCONST_ARYBASE; my $unbacked = re_unback($self->const_sv($op)->as_string); return re_uninterp(escape_re($unbacked)); } elsif ($type eq "concat") { my $first = $self->re_dq($op->first); my $last = $self->re_dq($op->last); return re_dq_disambiguate($first, $last); } elsif ($type eq "multiconcat") { return $self->do_multiconcat($op, 26, 2); } elsif ($type eq "uc") { return '\U' . $self->re_dq($op->first->sibling) . '\E'; } elsif ($type eq "lc") { return '\L' . $self->re_dq($op->first->sibling) . '\E'; } elsif ($type eq "ucfirst") { return '\u' . $self->re_dq($op->first->sibling); } elsif ($type eq "lcfirst") { return '\l' . $self->re_dq($op->first->sibling); } elsif ($type eq "quotemeta") { return '\Q' . $self->re_dq($op->first->sibling) . '\E'; } elsif ($type eq "fc") { return '\F' . $self->re_dq($op->first->sibling) . '\E'; } elsif ($type eq "join") { return $self->deparse($op->last, 26); # was join($", @ary) } else { my $ret = $self->deparse($op, 26); $ret =~ s/^\$([(|)])\z/\${$1}/ # $( $| $) need braces or $ret =~ s/^\@([-+])\z/\@{$1}/; # @- @+ need braces return $ret; } } sub pure_string { my ($self, $op) = @_; return 0 if null $op; my $type = $op->name; if ($type eq 'const' || $type eq 'av2arylen') { return 1; } elsif ($type =~ /^(?:[ul]c(first)?|fc)$/ || $type eq 'quotemeta') { return $self->pure_string($op->first->sibling); } elsif ($type eq 'join') { my $join_op = $op->first->sibling; # Skip pushmark return 0 unless $join_op->name eq 'null' && $join_op->targ == OP_RV2SV; my $gvop = $join_op->first; return 0 unless $gvop->name eq 'gvsv'; return 0 unless '"' eq $self->gv_name($self->gv_or_padgv($gvop)); return 0 unless ${$join_op->sibling} eq ${$op->last}; return 0 unless $op->last->name =~ /^(?:[ah]slice|(?:rv2|pad)av)$/; } elsif ($type eq 'concat') { return $self->pure_string($op->first) && $self->pure_string($op->last); } elsif ($type eq 'multiconcat') { my ($kid, @kids); for ($kid = $op->first; !null $kid; $kid = $kid->sibling) { # skip the consts and/or padsv we've optimised away push @kids, $kid unless $kid->type == OP_NULL && ( $kid->targ == OP_PADSV || $kid->targ == OP_CONST || $kid->targ == OP_PUSHMARK); } if ($op->flags & OPf_STACKED) { # remove expr from @kids where 'expr = ...' or 'expr .= ....' if ($op->private & OPpMULTICONCAT_APPEND) { shift(@kids); } else { pop(@kids); } } for (@kids) { return 0 unless $self->pure_string($_); } return 1; } elsif (is_scalar($op) || $type =~ /^[ah]elem$/) { return 1; } elsif ($type eq "null" and $op->can('first') and not null $op->first) { my $first = $op->first; return 1 if $first->name eq "multideref"; return 1 if $first->name eq "aelemfast_lex"; if ( $first->name eq "null" and $first->can('first') and not null $first->first and $first->first->name eq "aelemfast" ) { return 1; } } return 0; } sub code_list { my ($self,$op,$cv) = @_; # localise stuff relating to the current sub $cv and local($self->{'curcv'}) = $cv, local($self->{'curcvlex'}), local(@$self{qw'curstash warnings hints hinthash curcop'}) = @$self{qw'curstash warnings hints hinthash curcop'}; my $re; for ($op = $op->first->sibling; !null($op); $op = $op->sibling) { if ($op->name eq 'null' and $op->flags & OPf_SPECIAL) { my $scope = $op->first; # 0 context (last arg to scopeop) means statement context, so # the contents of the block will not be wrapped in do{...}. my $block = scopeop($scope->first->name eq "enter", $self, $scope, 0); # next op is the source code of the block $op = $op->sibling; $re .= ($self->const_sv($op)->PV =~ m|^(\(\?\??\{)|)[0]; my $multiline = $block =~ /\n/; $re .= $multiline ? "\n\t" : ' '; $re .= $block; $re .= $multiline ? "\n\b})" : " })"; } else { $re = re_dq_disambiguate($re, $self->re_dq($op)); } } $re; } sub regcomp { my $self = shift; my($op, $cx) = @_; my $kid = $op->first; $kid = $kid->first if $kid->name eq "regcmaybe"; $kid = $kid->first if $kid->name eq "regcreset"; my $kname = $kid->name; if ($kname eq "null" and !null($kid->first) and $kid->first->name eq 'pushmark') { my $str = ''; $kid = $kid->first->sibling; while (!null($kid)) { my $first = $str; my $last = $self->re_dq($kid); $str = re_dq_disambiguate($first, $last); $kid = $kid->sibling; } return $str, 1; } return ($self->re_dq($kid), 1) if $kname =~ /^(?:rv2|pad)av/ or $self->pure_string($kid); return ($self->deparse($kid, $cx), 0); } sub pp_regcomp { my ($self, $op, $cx) = @_; return (($self->regcomp($op, $cx, 0))[0]); } sub re_flags { my ($self, $op) = @_; my $flags = ''; my $pmflags = $op->pmflags; if (!$pmflags) { my $re = $op->pmregexp; if ($$re) { $pmflags = $re->compflags; } } $flags .= "g" if $pmflags & PMf_GLOBAL; $flags .= "i" if $pmflags & PMf_FOLD; $flags .= "m" if $pmflags & PMf_MULTILINE; $flags .= "o" if $pmflags & PMf_KEEP; $flags .= "s" if $pmflags & PMf_SINGLELINE; $flags .= "x" if $pmflags & PMf_EXTENDED; $flags .= "x" if $pmflags & PMf_EXTENDED_MORE; $flags .= "p" if $pmflags & PMf_KEEPCOPY; $flags .= "n" if $pmflags & PMf_NOCAPTURE; if (my $charset = $pmflags & PMf_CHARSET) { # Hardcoding this is fragile, but B does not yet export the # constants we need. $flags .= qw(d l u a aa)[$charset >> 7] } # The /d flag is indicated by 0; only show it if necessary. elsif ($self->{hinthash} and $self->{hinthash}{reflags_charset} || $self->{hinthash}{feature_unicode} or $self->{hints} & $feature::hint_mask && ($self->{hints} & $feature::hint_mask) != $feature::hint_mask && $self->{hints} & $feature::hint_uni8bit ) { $flags .= 'd'; } $flags; } # osmic acid -- see osmium tetroxide my %matchwords; map($matchwords{join "", sort split //, $_} = $_, 'cig', 'cog', 'cos', 'cogs', 'cox', 'go', 'is', 'ism', 'iso', 'mig', 'mix', 'osmic', 'ox', 'sic', 'sig', 'six', 'smog', 'so', 'soc', 'sog', 'xi', 'soup', 'soupmix'); # When deparsing a regular expression with code blocks, we have to look in # various places to find the blocks. # # For qr/(?{...})/ without interpolation, the CV is under $qr->qr_anoncv # and the code list (list of blocks and constants, maybe vars) is under # $cv->ROOT->first->code_list: # ./perl -Ilib -MB -e 'use O "Concise", B::svref_2object(sub {qr/(?{die})/})->ROOT->first->first->sibling->pmregexp->qr_anoncv->object_2svref' # # For qr/$a(?{...})/ with interpolation, the code list is more accessible, # under $pmop->code_list, but the $cv is something you have to dig for in # the regcomp op’s kids: # ./perl -Ilib -mO=Concise -e 'qr/$a(?{die})/' # # For m// and split //, things are much simpler. There is no CV. The code # list is under $pmop->code_list. sub matchop { my $self = shift; my($op, $cx, $name, $delim) = @_; my $kid = $op->first; my ($binop, $var, $re) = ("", "", ""); if ($op->name ne 'split' && $op->flags & OPf_STACKED) { $binop = 1; $var = $self->deparse($kid, 20); $kid = $kid->sibling; } # not $name; $name will be 'm' for both match and split elsif ($op->name eq 'match' and my $targ = $op->targ) { $binop = 1; $var = $self->padname($targ); } my $quote = 1; my $pmflags = $op->pmflags; my $rhs_bound_to_defsv; my ($cv, $bregexp); my $have_kid = !null $kid; # Check for code blocks first if (not null my $code_list = $op->code_list) { $re = $self->code_list($code_list, $op->name eq 'qr' ? $self->padval( $kid->first # ex-list ->first # pushmark ->sibling # entersub ->first # ex-list ->first # pushmark ->sibling # srefgen ->first # ex-list ->first # anoncode ->targ ) : undef); } elsif (${$bregexp = $op->pmregexp} && ${$cv = $bregexp->qr_anoncv}) { my $patop = $cv->ROOT # leavesub ->first # qr ->code_list;# list $re = $self->code_list($patop, $cv); } elsif (!$have_kid) { $re = re_uninterp(escape_re(re_unback($op->precomp))); } elsif ($kid->name ne 'regcomp') { if ($op->name eq 'split') { # split has other kids, not just regcomp $re = re_uninterp(escape_re(re_unback($op->precomp))); } else { carp("found ".$kid->name." where regcomp expected"); } } else { ($re, $quote) = $self->regcomp($kid, 21); } if ($have_kid and $kid->name eq 'regcomp') { my $matchop = $kid->first; if ($matchop->name eq 'regcreset') { $matchop = $matchop->first; } if ($matchop->name =~ /^(?:match|transr?|subst)\z/ && $matchop->flags & OPf_SPECIAL) { $rhs_bound_to_defsv = 1; } } my $flags = ""; $flags .= "c" if $pmflags & PMf_CONTINUE; $flags .= $self->re_flags($op); $flags = join '', sort split //, $flags; $flags = $matchwords{$flags} if $matchwords{$flags}; if ($pmflags & PMf_ONCE) { # only one kind of delimiter works here $re =~ s/\?/\\?/g; $re = $self->keyword("m") . "?$re?"; # explicit 'm' is required } elsif ($quote) { $re = single_delim($name, $delim, $re, $self); } $re = $re . $flags if $quote; if ($binop) { return $self->maybe_parens( $rhs_bound_to_defsv ? "$var =~ (\$_ =~ $re)" : "$var =~ $re", $cx, 20 ); } else { return $re; } } sub pp_match { matchop(@_, "m", "/") } sub pp_qr { matchop(@_, "qr", "") } sub pp_runcv { unop(@_, "__SUB__"); } sub pp_split { my $self = shift; my($op, $cx) = @_; my($kid, @exprs, $ary, $expr); my $stacked = $op->flags & OPf_STACKED; $kid = $op->first; $kid = $kid->sibling if $kid->name eq 'regcomp'; for (; !null($kid); $kid = $kid->sibling) { push @exprs, $self->deparse($kid, 6); } unshift @exprs, $self->matchop($op, $cx, "m", "/"); if ($op->private & OPpSPLIT_ASSIGN) { # With C<@array = split(/pat/, str);>, # array is stored in split's pmreplroot; either # as an integer index into the pad (for a lexical array) # or as GV for a package array (which will be a pad index # on threaded builds) # With my/our @array = split(/pat/, str), the array is instead # accessed via an extra padav/rv2av op at the end of the # split's kid ops. if ($stacked) { $ary = pop @exprs; } else { if ($op->private & OPpSPLIT_LEX) { $ary = $self->padname($op->pmreplroot); } else { # union with op_pmtargetoff, op_pmtargetgv my $gv = $op->pmreplroot; $gv = $self->padval($gv) if !ref($gv); $ary = $self->maybe_local(@_, $self->stash_variable('@', $self->gv_name($gv), $cx)) } if ($op->private & OPpLVAL_INTRO) { $ary = $op->private & OPpSPLIT_LEX ? "my $ary" : "local $ary"; } } } # handle special case of split(), and split(' ') that compiles to /\s+/ $exprs[0] = q{' '} if ($op->reflags // 0) & RXf_SKIPWHITE(); $expr = "split(" . join(", ", @exprs) . ")"; if ($ary) { return $self->maybe_parens("$ary = $expr", $cx, 7); } else { return $expr; } } # oxime -- any of various compounds obtained chiefly by the action of # hydroxylamine on aldehydes and ketones and characterized by the # bivalent grouping C=NOH [Webster's Tenth] my %substwords; map($substwords{join "", sort split //, $_} = $_, 'ego', 'egoism', 'em', 'es', 'ex', 'exes', 'gee', 'go', 'goes', 'ie', 'ism', 'iso', 'me', 'meese', 'meso', 'mig', 'mix', 'os', 'ox', 'oxime', 'see', 'seem', 'seg', 'sex', 'sig', 'six', 'smog', 'sog', 'some', 'xi', 'rogue', 'sir', 'rise', 'smore', 'more', 'seer', 'rome', 'gore', 'grim', 'grime', 'or', 'rose', 'rosie'); sub pp_subst { my $self = shift; my($op, $cx) = @_; my $kid = $op->first; my($binop, $var, $re, $repl) = ("", "", "", ""); if ($op->flags & OPf_STACKED) { $binop = 1; $var = $self->deparse($kid, 20); $kid = $kid->sibling; } elsif (my $targ = $op->targ) { $binop = 1; $var = $self->padname($targ); } my $flags = ""; my $pmflags = $op->pmflags; if (null($op->pmreplroot)) { $repl = $kid; $kid = $kid->sibling; } else { $repl = $op->pmreplroot->first; # skip substcont } while ($repl->name eq "entereval") { $repl = $repl->first; $flags .= "e"; } { local $self->{in_subst_repl} = 1; if ($pmflags & PMf_EVAL) { $repl = $self->deparse($repl->first, 0); } else { $repl = $self->dq($repl); } } if (not null my $code_list = $op->code_list) { $re = $self->code_list($code_list); } elsif (null $kid) { $re = re_uninterp(escape_re(re_unback($op->precomp))); } else { ($re) = $self->regcomp($kid, 1); } $flags .= "r" if $pmflags & PMf_NONDESTRUCT; $flags .= "e" if $pmflags & PMf_EVAL; $flags .= $self->re_flags($op); $flags = join '', sort split //, $flags; $flags = $substwords{$flags} if $substwords{$flags}; my $core_s = $self->keyword("s"); # maybe CORE::s if ($binop) { return $self->maybe_parens("$var =~ $core_s" . double_delim($re, $repl) . $flags, $cx, 20); } else { return "$core_s". double_delim($re, $repl) . $flags; } } sub is_lexical_subs { my (@ops) = shift; for my $op (@ops) { return 0 if $op->name !~ /\A(?:introcv|clonecv)\z/; } return 1; } # Pretend these two ops do not exist. The perl parser adds them to the # beginning of any block containing my-sub declarations, whereas we handle # the subs in pad_subs and next_todo. *pp_clonecv = *pp_introcv; sub pp_introcv { my $self = shift; my($op, $cx) = @_; # For now, deparsing doesn't worry about the distinction between introcv # and clonecv, so pretend this op doesn't exist: return ''; } sub pp_padcv { my $self = shift; my($op, $cx) = @_; return $self->padany($op); } my %lvref_funnies = ( OPpLVREF_SV, => '$', OPpLVREF_AV, => '@', OPpLVREF_HV, => '%', OPpLVREF_CV, => '&', ); sub pp_refassign { my ($self, $op, $cx) = @_; my $left; if ($op->private & OPpLVREF_ELEM) { $left = $op->first->sibling; $left = maybe_local(@_, elem($self, $left, undef, $left->targ == OP_AELEM ? qw([ ] padav) : qw({ } padhv))); } elsif ($op->flags & OPf_STACKED) { $left = maybe_local(@_, $lvref_funnies{$op->private & OPpLVREF_TYPE} . $self->deparse($op->first->sibling)); } else { $left = &pp_padsv; } my $right = $self->deparse_binop_right($op, $op->first, 7); return $self->maybe_parens("\\$left = $right", $cx, 7); } sub pp_lvref { my ($self, $op, $cx) = @_; my $code; if ($op->private & OPpLVREF_ELEM) { $code = $op->first->name =~ /av\z/ ? &pp_aelem : &pp_helem; } elsif ($op->flags & OPf_STACKED) { $code = maybe_local(@_, $lvref_funnies{$op->private & OPpLVREF_TYPE} . $self->deparse($op->first)); } else { $code = &pp_padsv; } "\\$code"; } sub pp_lvrefslice { my ($self, $op, $cx) = @_; '\\' . ($op->last->name =~ /av\z/ ? &pp_aslice : &pp_hslice); } sub pp_lvavref { my ($self, $op, $cx) = @_; '\\(' . ($op->flags & OPf_STACKED ? maybe_local(@_, rv2x(@_, "\@")) : &pp_padsv) . ')' } sub pp_argcheck { my $self = shift; my($op, $cx) = @_; my ($params, $opt_params, $slurpy) = $op->aux_list($self->{curcv}); my $mandatory = $params - $opt_params; my $check = ''; $check .= < 0; die sprintf("Too few arguments for subroutine at %s line %d.\\n", (caller)[1, 2]) unless \@_ >= $mandatory; EOF my $cond = ($params & 1) ? 'unless' : 'if'; $check .= < $params && ((\@_ - $params) & 1); EOF $check =~ s/;\n\z//; return $check; } sub pp_argelem { my $self = shift; my($op, $cx) = @_; my $var = $self->padname($op->targ); my $ix = $op->string($self->{curcv}); my $expr; if ($op->flags & OPf_KIDS) { $expr = $self->deparse($op->first, 7); } elsif ($var =~ /^[@%]/) { $expr = $ix ? "\@_[$ix .. \$#_]" : '@_'; } else { $expr = "\$_[$ix]"; } return "my $var = $expr"; } sub pp_argdefelem { my $self = shift; my($op, $cx) = @_; my $ix = $op->targ; my $expr = "\@_ >= " . ($ix+1) . " ? \$_[$ix] : "; my $def = $self->deparse($op->first, 7); $def = "($def)" if $op->first->flags & OPf_PARENS; $expr .= $self->deparse($op->first, $cx); return $expr; } 1; __END__ # -*- buffer-read-only: t -*- # # lib/B/Op_private.pm # # Copyright (C) 2014 by Larry Wall and others # # You may distribute under the terms of either the GNU General Public # License or the Artistic License, as specified in the README file. # # !!!!!!! DO NOT EDIT THIS FILE !!!!!!! # This file is built by regen/opcode.pl from data in # regen/op_private and pod embedded in regen/opcode.pl. # Any changes made here will be lost! package B::Op_private; our %bits; our $VERSION = "5.028001"; $bits{$_}{3} = 'OPpENTERSUB_AMPER' for qw(entersub rv2cv); $bits{$_}{6} = 'OPpENTERSUB_DB' for qw(entersub rv2cv); $bits{$_}{2} = 'OPpENTERSUB_HASTARG' for qw(entersub rv2cv); $bits{$_}{6} = 'OPpFLIP_LINENUM' for qw(flip flop); $bits{$_}{1} = 'OPpFT_ACCESS' for qw(fteexec fteread ftewrite ftrexec ftrread ftrwrite); $bits{$_}{4} = 'OPpFT_AFTER_t' for qw(ftatime ftbinary ftblk ftchr ftctime ftdir fteexec fteowned fteread ftewrite ftfile ftis ftlink ftmtime ftpipe ftrexec ftrowned ftrread ftrwrite ftsgid ftsize ftsock ftsuid ftsvtx fttext fttty ftzero); $bits{$_}{2} = 'OPpFT_STACKED' for qw(ftatime ftbinary ftblk ftchr ftctime ftdir fteexec fteowned fteread ftewrite ftfile ftis ftlink ftmtime ftpipe ftrexec ftrowned ftrread ftrwrite ftsgid ftsize ftsock ftsuid ftsvtx fttext fttty ftzero); $bits{$_}{3} = 'OPpFT_STACKING' for qw(ftatime ftbinary ftblk ftchr ftctime ftdir fteexec fteowned fteread ftewrite ftfile ftis ftlink ftmtime ftpipe ftrexec ftrowned ftrread ftrwrite ftsgid ftsize ftsock ftsuid ftsvtx fttext fttty ftzero); $bits{$_}{1} = 'OPpHINT_STRICT_REFS' for qw(entersub multideref rv2av rv2cv rv2gv rv2hv rv2sv); $bits{$_}{5} = 'OPpHUSH_VMSISH' for qw(dbstate nextstate); $bits{$_}{6} = 'OPpINDEX_BOOLNEG' for qw(index rindex); $bits{$_}{1} = 'OPpITER_REVERSED' for qw(enteriter iter); $bits{$_}{7} = 'OPpLVALUE' for qw(leave leaveloop); $bits{$_}{6} = 'OPpLVAL_DEFER' for qw(aelem helem multideref); $bits{$_}{7} = 'OPpLVAL_INTRO' for qw(aelem aslice cond_expr delete enteriter entersub gvsv helem hslice list lvavref lvref lvrefslice multiconcat multideref padav padhv padrange padsv pushmark refassign rv2av rv2gv rv2hv rv2sv split); $bits{$_}{2} = 'OPpLVREF_ELEM' for qw(lvref refassign); $bits{$_}{3} = 'OPpLVREF_ITER' for qw(lvref refassign); $bits{$_}{3} = 'OPpMAYBE_LVSUB' for qw(aassign aelem akeys aslice av2arylen avhvswitch helem hslice keys kvaslice kvhslice multideref padav padhv pos rv2av rv2gv rv2hv substr values vec); $bits{$_}{4} = 'OPpMAYBE_TRUEBOOL' for qw(padhv ref rv2hv); $bits{$_}{7} = 'OPpOFFBYONE' for qw(caller runcv wantarray); $bits{$_}{5} = 'OPpOPEN_IN_CRLF' for qw(backtick open); $bits{$_}{4} = 'OPpOPEN_IN_RAW' for qw(backtick open); $bits{$_}{7} = 'OPpOPEN_OUT_CRLF' for qw(backtick open); $bits{$_}{6} = 'OPpOPEN_OUT_RAW' for qw(backtick open); $bits{$_}{6} = 'OPpOUR_INTRO' for qw(enteriter gvsv rv2av rv2hv rv2sv split); $bits{$_}{6} = 'OPpPAD_STATE' for qw(lvavref lvref padav padhv padsv pushmark refassign); $bits{$_}{7} = 'OPpPV_IS_UTF8' for qw(dump goto last next redo); $bits{$_}{6} = 'OPpREFCOUNTED' for qw(leave leaveeval leavesub leavesublv leavewrite); $bits{$_}{2} = 'OPpSLICEWARNING' for qw(aslice hslice padav padhv rv2av rv2hv); $bits{$_}{4} = 'OPpTARGET_MY' for qw(abs add atan2 chdir chmod chomp chown chr chroot concat cos crypt divide exec exp flock getpgrp getppid getpriority hex i_add i_divide i_modulo i_multiply i_subtract index int kill left_shift length link log mkdir modulo multiconcat multiply nbit_and nbit_or nbit_xor ncomplement oct ord pow push rand rename right_shift rindex rmdir schomp scomplement setpgrp setpriority sin sleep sqrt srand stringify subtract symlink system time unlink unshift utime wait waitpid); $bits{$_}{5} = 'OPpTRANS_COMPLEMENT' for qw(trans transr); $bits{$_}{7} = 'OPpTRANS_DELETE' for qw(trans transr); $bits{$_}{0} = 'OPpTRANS_FROM_UTF' for qw(trans transr); $bits{$_}{6} = 'OPpTRANS_GROWS' for qw(trans transr); $bits{$_}{2} = 'OPpTRANS_IDENTICAL' for qw(trans transr); $bits{$_}{3} = 'OPpTRANS_SQUASH' for qw(trans transr); $bits{$_}{1} = 'OPpTRANS_TO_UTF' for qw(trans transr); $bits{$_}{5} = 'OPpTRUEBOOL' for qw(grepwhile index length padav padhv pos ref rindex rv2av rv2hv subst); my @bf = ( { label => '-', mask_def => 'OPpARG1_MASK', bitmin => 0, bitmax => 0, bitmask => 1, }, { label => '-', mask_def => 'OPpARG2_MASK', bitmin => 0, bitmax => 1, bitmask => 3, }, { label => 'offset', mask_def => 'OPpAVHVSWITCH_MASK', bitmin => 0, bitmax => 1, bitmask => 3, }, { label => '-', mask_def => 'OPpARG3_MASK', bitmin => 0, bitmax => 2, bitmask => 7, }, { label => '-', mask_def => 'OPpARG4_MASK', bitmin => 0, bitmax => 3, bitmask => 15, }, { label => 'range', mask_def => 'OPpPADRANGE_COUNTMASK', bitcount_def => 'OPpPADRANGE_COUNTSHIFT', bitmin => 0, bitmax => 6, bitmask => 127, }, { label => 'key', bitmin => 0, bitmax => 7, bitmask => 255, }, { mask_def => 'OPpARGELEM_MASK', bitmin => 1, bitmax => 2, bitmask => 6, enum => [ 0, 'OPpARGELEM_SV', 'SV', 1, 'OPpARGELEM_AV', 'AV', 2, 'OPpARGELEM_HV', 'HV', ], }, { mask_def => 'OPpDEREF', bitmin => 4, bitmax => 5, bitmask => 48, enum => [ 1, 'OPpDEREF_AV', 'DREFAV', 2, 'OPpDEREF_HV', 'DREFHV', 3, 'OPpDEREF_SV', 'DREFSV', ], }, { mask_def => 'OPpLVREF_TYPE', bitmin => 4, bitmax => 5, bitmask => 48, enum => [ 0, 'OPpLVREF_SV', 'SV', 1, 'OPpLVREF_AV', 'AV', 2, 'OPpLVREF_HV', 'HV', 3, 'OPpLVREF_CV', 'CV', ], }, ); @{$bits{aassign}}{6,5,4,2,1,0} = ('OPpASSIGN_COMMON_SCALAR', 'OPpASSIGN_COMMON_RC1', 'OPpASSIGN_COMMON_AGG', 'OPpASSIGN_TRUEBOOL', $bf[1], $bf[1]); $bits{abs}{0} = $bf[0]; @{$bits{accept}}{3,2,1,0} = ($bf[4], $bf[4], $bf[4], $bf[4]); @{$bits{add}}{1,0} = ($bf[1], $bf[1]); $bits{aeach}{0} = $bf[0]; @{$bits{aelem}}{5,4,1,0} = ($bf[8], $bf[8], $bf[1], $bf[1]); @{$bits{aelemfast}}{7,6,5,4,3,2,1,0} = ($bf[6], $bf[6], $bf[6], $bf[6], $bf[6], $bf[6], $bf[6], $bf[6]); @{$bits{aelemfast_lex}}{7,6,5,4,3,2,1,0} = ($bf[6], $bf[6], $bf[6], $bf[6], $bf[6], $bf[6], $bf[6], $bf[6]); $bits{akeys}{0} = $bf[0]; $bits{alarm}{0} = $bf[0]; $bits{and}{0} = $bf[0]; $bits{andassign}{0} = $bf[0]; $bits{anonconst}{0} = $bf[0]; @{$bits{anonhash}}{3,2,1,0} = ($bf[4], $bf[4], $bf[4], $bf[4]); @{$bits{anonlist}}{3,2,1,0} = ($bf[4], $bf[4], $bf[4], $bf[4]); $bits{argcheck}{0} = $bf[0]; $bits{argdefelem}{0} = $bf[0]; @{$bits{argelem}}{2,1,0} = ($bf[7], $bf[7], $bf[0]); @{$bits{atan2}}{3,2,1,0} = ($bf[4], $bf[4], $bf[4], $bf[4]); $bits{av2arylen}{0} = $bf[0]; $bits{avalues}{0} = $bf[0]; @{$bits{avhvswitch}}{1,0} = ($bf[2], $bf[2]); $bits{backtick}{0} = $bf[0]; @{$bits{bind}}{3,2,1,0} = ($bf[4], $bf[4], $bf[4], $bf[4]); @{$bits{binmode}}{3,2,1,0} = ($bf[4], $bf[4], $bf[4], $bf[4]); @{$bits{bit_and}}{1,0} = ($bf[1], $bf[1]); @{$bits{bit_or}}{1,0} = ($bf[1], $bf[1]); @{$bits{bit_xor}}{1,0} = ($bf[1], $bf[1]); @{$bits{bless}}{3,2,1,0} = ($bf[4], $bf[4], $bf[4], $bf[4]); @{$bits{caller}}{3,2,1,0} = ($bf[4], $bf[4], $bf[4], $bf[4]); @{$bits{chdir}}{3,2,1,0} = ($bf[4], $bf[4], $bf[4], $bf[4]); @{$bits{chmod}}{3,2,1,0} = ($bf[4], $bf[4], $bf[4], $bf[4]); $bits{chomp}{0} = $bf[0]; $bits{chop}{0} = $bf[0]; @{$bits{chown}}{3,2,1,0} = ($bf[4], $bf[4], $bf[4], $bf[4]); $bits{chr}{0} = $bf[0]; $bits{chroot}{0} = $bf[0]; @{$bits{close}}{3,2,1,0} = ($bf[4], $bf[4], $bf[4], $bf[4]); $bits{closedir}{0} = $bf[0]; $bits{complement}{0} = $bf[0]; @{$bits{concat}}{6,1,0} = ('OPpCONCAT_NESTED', $bf[1], $bf[1]); $bits{cond_expr}{0} = $bf[0]; @{$bits{connect}}{3,2,1,0} = ($bf[4], $bf[4], $bf[4], $bf[4]); @{$bits{const}}{6,4,3,2,1} = ('OPpCONST_BARE', 'OPpCONST_ENTERED', 'OPpCONST_STRICT', 'OPpCONST_SHORTCIRCUIT', 'OPpCONST_NOVER'); @{$bits{coreargs}}{7,6,1,0} = ('OPpCOREARGS_PUSHMARK', 'OPpCOREARGS_SCALARMOD', 'OPpCOREARGS_DEREF2', 'OPpCOREARGS_DEREF1'); $bits{cos}{0} = $bf[0]; @{$bits{crypt}}{3,2,1,0} = ($bf[4], $bf[4], $bf[4], $bf[4]); $bits{dbmclose}{0} = $bf[0]; @{$bits{dbmopen}}{3,2,1,0} = ($bf[4], $bf[4], $bf[4], $bf[4]); $bits{defined}{0} = $bf[0]; @{$bits{delete}}{6,5,0} = ('OPpSLICE', 'OPpKVSLICE', $bf[0]); @{$bits{die}}{3,2,1,0} = ($bf[4], $bf[4], $bf[4], $bf[4]); @{$bits{divide}}{1,0} = ($bf[1], $bf[1]); $bits{dofile}{0} = $bf[0]; $bits{dor}{0} = $bf[0]; $bits{dorassign}{0} = $bf[0]; $bits{dump}{0} = $bf[0]; $bits{each}{0} = $bf[0]; @{$bits{entereval}}{5,4,3,2,1,0} = ('OPpEVAL_RE_REPARSING', 'OPpEVAL_COPHH', 'OPpEVAL_BYTES', 'OPpEVAL_UNICODE', 'OPpEVAL_HAS_HH', $bf[0]); $bits{entergiven}{0} = $bf[0]; $bits{enteriter}{3} = 'OPpITER_DEF'; @{$bits{entersub}}{5,4,0} = ($bf[8], $bf[8], 'OPpENTERSUB_INARGS'); $bits{entertry}{0} = $bf[0]; $bits{enterwhen}{0} = $bf[0]; @{$bits{enterwrite}}{3,2,1,0} = ($bf[4], $bf[4], $bf[4], $bf[4]); @{$bits{eof}}{3,2,1,0} = ($bf[4], $bf[4], $bf[4], $bf[4]); @{$bits{eq}}{1,0} = ($bf[1], $bf[1]); @{$bits{exec}}{3,2,1,0} = ($bf[4], $bf[4], $bf[4], $bf[4]); @{$bits{exists}}{6,0} = ('OPpEXISTS_SUB', $bf[0]); @{$bits{exit}}{3,2,1,0} = ($bf[4], $bf[4], $bf[4], $bf[4]); $bits{exp}{0} = $bf[0]; $bits{fc}{0} = $bf[0]; @{$bits{fcntl}}{3,2,1,0} = ($bf[4], $bf[4], $bf[4], $bf[4]); @{$bits{fileno}}{3,2,1,0} = ($bf[4], $bf[4], $bf[4], $bf[4]); $bits{flip}{0} = $bf[0]; @{$bits{flock}}{3,2,1,0} = ($bf[4], $bf[4], $bf[4], $bf[4]); $bits{flop}{0} = $bf[0]; @{$bits{formline}}{3,2,1,0} = ($bf[4], $bf[4], $bf[4], $bf[4]); $bits{ftatime}{0} = $bf[0]; $bits{ftbinary}{0} = $bf[0]; $bits{ftblk}{0} = $bf[0]; $bits{ftchr}{0} = $bf[0]; $bits{ftctime}{0} = $bf[0]; $bits{ftdir}{0} = $bf[0]; $bits{fteexec}{0} = $bf[0]; $bits{fteowned}{0} = $bf[0]; $bits{fteread}{0} = $bf[0]; $bits{ftewrite}{0} = $bf[0]; $bits{ftfile}{0} = $bf[0]; $bits{ftis}{0} = $bf[0]; $bits{ftlink}{0} = $bf[0]; $bits{ftmtime}{0} = $bf[0]; $bits{ftpipe}{0} = $bf[0]; $bits{ftrexec}{0} = $bf[0]; $bits{ftrowned}{0} = $bf[0]; $bits{ftrread}{0} = $bf[0]; $bits{ftrwrite}{0} = $bf[0]; $bits{ftsgid}{0} = $bf[0]; $bits{ftsize}{0} = $bf[0]; $bits{ftsock}{0} = $bf[0]; $bits{ftsuid}{0} = $bf[0]; $bits{ftsvtx}{0} = $bf[0]; $bits{fttext}{0} = $bf[0]; $bits{fttty}{0} = $bf[0]; $bits{ftzero}{0} = $bf[0]; @{$bits{ge}}{1,0} = ($bf[1], $bf[1]); @{$bits{gelem}}{1,0} = ($bf[1], $bf[1]); @{$bits{getc}}{3,2,1,0} = ($bf[4], $bf[4], $bf[4], $bf[4]); $bits{getpeername}{0} = $bf[0]; @{$bits{getpgrp}}{3,2,1,0} = ($bf[4], $bf[4], $bf[4], $bf[4]); @{$bits{getpriority}}{3,2,1,0} = ($bf[4], $bf[4], $bf[4], $bf[4]); $bits{getsockname}{0} = $bf[0]; $bits{ggrgid}{0} = $bf[0]; $bits{ggrnam}{0} = $bf[0]; @{$bits{ghbyaddr}}{3,2,1,0} = ($bf[4], $bf[4], $bf[4], $bf[4]); $bits{ghbyname}{0} = $bf[0]; @{$bits{glob}}{3,2,1,0} = ($bf[4], $bf[4], $bf[4], $bf[4]); @{$bits{gmtime}}{3,2,1,0} = ($bf[4], $bf[4], $bf[4], $bf[4]); @{$bits{gnbyaddr}}{3,2,1,0} = ($bf[4], $bf[4], $bf[4], $bf[4]); $bits{gnbyname}{0} = $bf[0]; $bits{goto}{0} = $bf[0]; $bits{gpbyname}{0} = $bf[0]; @{$bits{gpbynumber}}{3,2,1,0} = ($bf[4], $bf[4], $bf[4], $bf[4]); $bits{gpwnam}{0} = $bf[0]; $bits{gpwuid}{0} = $bf[0]; $bits{grepstart}{0} = $bf[0]; $bits{grepwhile}{0} = $bf[0]; @{$bits{gsbyname}}{3,2,1,0} = ($bf[4], $bf[4], $bf[4], $bf[4]); @{$bits{gsbyport}}{3,2,1,0} = ($bf[4], $bf[4], $bf[4], $bf[4]); @{$bits{gsockopt}}{3,2,1,0} = ($bf[4], $bf[4], $bf[4], $bf[4]); @{$bits{gt}}{1,0} = ($bf[1], $bf[1]); $bits{gv}{5} = 'OPpEARLY_CV'; @{$bits{helem}}{5,4,1,0} = ($bf[8], $bf[8], $bf[1], $bf[1]); $bits{hex}{0} = $bf[0]; @{$bits{i_add}}{1,0} = ($bf[1], $bf[1]); @{$bits{i_divide}}{1,0} = ($bf[1], $bf[1]); @{$bits{i_eq}}{1,0} = ($bf[1], $bf[1]); @{$bits{i_ge}}{1,0} = ($bf[1], $bf[1]); @{$bits{i_gt}}{1,0} = ($bf[1], $bf[1]); @{$bits{i_le}}{1,0} = ($bf[1], $bf[1]); @{$bits{i_lt}}{1,0} = ($bf[1], $bf[1]); @{$bits{i_modulo}}{1,0} = ($bf[1], $bf[1]); @{$bits{i_multiply}}{1,0} = ($bf[1], $bf[1]); @{$bits{i_ncmp}}{1,0} = ($bf[1], $bf[1]); @{$bits{i_ne}}{1,0} = ($bf[1], $bf[1]); $bits{i_negate}{0} = $bf[0]; $bits{i_postdec}{0} = $bf[0]; $bits{i_postinc}{0} = $bf[0]; $bits{i_predec}{0} = $bf[0]; $bits{i_preinc}{0} = $bf[0]; @{$bits{i_subtract}}{1,0} = ($bf[1], $bf[1]); @{$bits{index}}{3,2,1,0} = ($bf[4], $bf[4], $bf[4], $bf[4]); $bits{int}{0} = $bf[0]; @{$bits{ioctl}}{3,2,1,0} = ($bf[4], $bf[4], $bf[4], $bf[4]); @{$bits{join}}{3,2,1,0} = ($bf[4], $bf[4], $bf[4], $bf[4]); $bits{keys}{0} = $bf[0]; @{$bits{kill}}{3,2,1,0} = ($bf[4], $bf[4], $bf[4], $bf[4]); $bits{last}{0} = $bf[0]; $bits{lc}{0} = $bf[0]; $bits{lcfirst}{0} = $bf[0]; @{$bits{le}}{1,0} = ($bf[1], $bf[1]); $bits{leaveeval}{0} = $bf[0]; $bits{leavegiven}{0} = $bf[0]; @{$bits{leaveloop}}{1,0} = ($bf[1], $bf[1]); $bits{leavesub}{0} = $bf[0]; $bits{leavesublv}{0} = $bf[0]; $bits{leavewhen}{0} = $bf[0]; $bits{leavewrite}{0} = $bf[0]; @{$bits{left_shift}}{1,0} = ($bf[1], $bf[1]); $bits{length}{0} = $bf[0]; @{$bits{link}}{3,2,1,0} = ($bf[4], $bf[4], $bf[4], $bf[4]); $bits{list}{6} = 'OPpLIST_GUESSED'; @{$bits{listen}}{3,2,1,0} = ($bf[4], $bf[4], $bf[4], $bf[4]); $bits{localtime}{0} = $bf[0]; $bits{lock}{0} = $bf[0]; $bits{log}{0} = $bf[0]; @{$bits{lslice}}{1,0} = ($bf[1], $bf[1]); $bits{lstat}{0} = $bf[0]; @{$bits{lt}}{1,0} = ($bf[1], $bf[1]); $bits{lvavref}{0} = $bf[0]; @{$bits{lvref}}{5,4,0} = ($bf[9], $bf[9], $bf[0]); $bits{mapstart}{0} = $bf[0]; $bits{mapwhile}{0} = $bf[0]; $bits{method}{0} = $bf[0]; $bits{method_named}{0} = $bf[0]; $bits{method_redir}{0} = $bf[0]; $bits{method_redir_super}{0} = $bf[0]; $bits{method_super}{0} = $bf[0]; @{$bits{mkdir}}{3,2,1,0} = ($bf[4], $bf[4], $bf[4], $bf[4]); @{$bits{modulo}}{1,0} = ($bf[1], $bf[1]); @{$bits{msgctl}}{3,2,1,0} = ($bf[4], $bf[4], $bf[4], $bf[4]); @{$bits{msgget}}{3,2,1,0} = ($bf[4], $bf[4], $bf[4], $bf[4]); @{$bits{msgrcv}}{3,2,1,0} = ($bf[4], $bf[4], $bf[4], $bf[4]); @{$bits{msgsnd}}{3,2,1,0} = ($bf[4], $bf[4], $bf[4], $bf[4]); @{$bits{multiconcat}}{6,5,3,0} = ('OPpMULTICONCAT_APPEND', 'OPpMULTICONCAT_FAKE', 'OPpMULTICONCAT_STRINGIFY', $bf[0]); @{$bits{multideref}}{5,4,0} = ('OPpMULTIDEREF_DELETE', 'OPpMULTIDEREF_EXISTS', $bf[0]); @{$bits{multiply}}{1,0} = ($bf[1], $bf[1]); @{$bits{nbit_and}}{1,0} = ($bf[1], $bf[1]); @{$bits{nbit_or}}{1,0} = ($bf[1], $bf[1]); @{$bits{nbit_xor}}{1,0} = ($bf[1], $bf[1]); @{$bits{ncmp}}{1,0} = ($bf[1], $bf[1]); $bits{ncomplement}{0} = $bf[0]; @{$bits{ne}}{1,0} = ($bf[1], $bf[1]); $bits{negate}{0} = $bf[0]; $bits{next}{0} = $bf[0]; $bits{not}{0} = $bf[0]; $bits{oct}{0} = $bf[0]; $bits{once}{0} = $bf[0]; @{$bits{open}}{3,2,1,0} = ($bf[4], $bf[4], $bf[4], $bf[4]); @{$bits{open_dir}}{3,2,1,0} = ($bf[4], $bf[4], $bf[4], $bf[4]); $bits{or}{0} = $bf[0]; $bits{orassign}{0} = $bf[0]; $bits{ord}{0} = $bf[0]; @{$bits{pack}}{3,2,1,0} = ($bf[4], $bf[4], $bf[4], $bf[4]); $bits{padhv}{0} = 'OPpPADHV_ISKEYS'; @{$bits{padrange}}{6,5,4,3,2,1,0} = ($bf[5], $bf[5], $bf[5], $bf[5], $bf[5], $bf[5], $bf[5]); @{$bits{padsv}}{5,4} = ($bf[8], $bf[8]); @{$bits{pipe_op}}{3,2,1,0} = ($bf[4], $bf[4], $bf[4], $bf[4]); $bits{pop}{0} = $bf[0]; $bits{pos}{0} = $bf[0]; $bits{postdec}{0} = $bf[0]; $bits{postinc}{0} = $bf[0]; @{$bits{pow}}{1,0} = ($bf[1], $bf[1]); $bits{predec}{0} = $bf[0]; $bits{preinc}{0} = $bf[0]; $bits{prototype}{0} = $bf[0]; @{$bits{push}}{3,2,1,0} = ($bf[4], $bf[4], $bf[4], $bf[4]); $bits{quotemeta}{0} = $bf[0]; @{$bits{rand}}{3,2,1,0} = ($bf[4], $bf[4], $bf[4], $bf[4]); $bits{range}{0} = $bf[0]; @{$bits{read}}{3,2,1,0} = ($bf[4], $bf[4], $bf[4], $bf[4]); $bits{readdir}{0} = $bf[0]; $bits{readline}{0} = $bf[0]; $bits{readlink}{0} = $bf[0]; @{$bits{recv}}{3,2,1,0} = ($bf[4], $bf[4], $bf[4], $bf[4]); $bits{redo}{0} = $bf[0]; $bits{ref}{0} = $bf[0]; @{$bits{refassign}}{5,4,1,0} = ($bf[9], $bf[9], $bf[1], $bf[1]); $bits{refgen}{0} = $bf[0]; $bits{regcmaybe}{0} = $bf[0]; $bits{regcomp}{0} = $bf[0]; $bits{regcreset}{0} = $bf[0]; @{$bits{rename}}{3,2,1,0} = ($bf[4], $bf[4], $bf[4], $bf[4]); @{$bits{repeat}}{6,1,0} = ('OPpREPEAT_DOLIST', $bf[1], $bf[1]); $bits{require}{0} = $bf[0]; @{$bits{reset}}{3,2,1,0} = ($bf[4], $bf[4], $bf[4], $bf[4]); @{$bits{reverse}}{3,0} = ('OPpREVERSE_INPLACE', $bf[0]); $bits{rewinddir}{0} = $bf[0]; @{$bits{right_shift}}{1,0} = ($bf[1], $bf[1]); @{$bits{rindex}}{3,2,1,0} = ($bf[4], $bf[4], $bf[4], $bf[4]); $bits{rmdir}{0} = $bf[0]; $bits{rv2av}{0} = $bf[0]; @{$bits{rv2cv}}{7,5,0} = ('OPpENTERSUB_NOPAREN', 'OPpMAY_RETURN_CONSTANT', $bf[0]); @{$bits{rv2gv}}{6,5,4,2,0} = ('OPpALLOW_FAKE', $bf[8], $bf[8], 'OPpDONT_INIT_GV', $bf[0]); $bits{rv2hv}{0} = 'OPpRV2HV_ISKEYS'; @{$bits{rv2sv}}{5,4,0} = ($bf[8], $bf[8], $bf[0]); @{$bits{sassign}}{7,6,1,0} = ('OPpASSIGN_CV_TO_GV', 'OPpASSIGN_BACKWARDS', $bf[1], $bf[1]); @{$bits{sbit_and}}{1,0} = ($bf[1], $bf[1]); @{$bits{sbit_or}}{1,0} = ($bf[1], $bf[1]); @{$bits{sbit_xor}}{1,0} = ($bf[1], $bf[1]); $bits{scalar}{0} = $bf[0]; $bits{schomp}{0} = $bf[0]; $bits{schop}{0} = $bf[0]; @{$bits{scmp}}{1,0} = ($bf[1], $bf[1]); $bits{scomplement}{0} = $bf[0]; @{$bits{seek}}{3,2,1,0} = ($bf[4], $bf[4], $bf[4], $bf[4]); @{$bits{seekdir}}{3,2,1,0} = ($bf[4], $bf[4], $bf[4], $bf[4]); @{$bits{select}}{3,2,1,0} = ($bf[4], $bf[4], $bf[4], $bf[4]); @{$bits{semctl}}{3,2,1,0} = ($bf[4], $bf[4], $bf[4], $bf[4]); @{$bits{semget}}{3,2,1,0} = ($bf[4], $bf[4], $bf[4], $bf[4]); @{$bits{semop}}{3,2,1,0} = ($bf[4], $bf[4], $bf[4], $bf[4]); @{$bits{send}}{3,2,1,0} = ($bf[4], $bf[4], $bf[4], $bf[4]); @{$bits{seq}}{1,0} = ($bf[1], $bf[1]); @{$bits{setpgrp}}{3,2,1,0} = ($bf[4], $bf[4], $bf[4], $bf[4]); @{$bits{setpriority}}{3,2,1,0} = ($bf[4], $bf[4], $bf[4], $bf[4]); @{$bits{sge}}{1,0} = ($bf[1], $bf[1]); @{$bits{sgt}}{1,0} = ($bf[1], $bf[1]); $bits{shift}{0} = $bf[0]; @{$bits{shmctl}}{3,2,1,0} = ($bf[4], $bf[4], $bf[4], $bf[4]); @{$bits{shmget}}{3,2,1,0} = ($bf[4], $bf[4], $bf[4], $bf[4]); @{$bits{shmread}}{3,2,1,0} = ($bf[4], $bf[4], $bf[4], $bf[4]); @{$bits{shmwrite}}{3,2,1,0} = ($bf[4], $bf[4], $bf[4], $bf[4]); $bits{shostent}{0} = $bf[0]; @{$bits{shutdown}}{3,2,1,0} = ($bf[4], $bf[4], $bf[4], $bf[4]); $bits{sin}{0} = $bf[0]; @{$bits{sle}}{1,0} = ($bf[1], $bf[1]); @{$bits{sleep}}{3,2,1,0} = ($bf[4], $bf[4], $bf[4], $bf[4]); @{$bits{slt}}{1,0} = ($bf[1], $bf[1]); @{$bits{smartmatch}}{1,0} = ($bf[1], $bf[1]); @{$bits{sne}}{1,0} = ($bf[1], $bf[1]); $bits{snetent}{0} = $bf[0]; @{$bits{socket}}{3,2,1,0} = ($bf[4], $bf[4], $bf[4], $bf[4]); @{$bits{sockpair}}{3,2,1,0} = ($bf[4], $bf[4], $bf[4], $bf[4]); @{$bits{sort}}{7,6,4,3,2,1,0} = ('OPpSORT_UNSTABLE', 'OPpSORT_STABLE', 'OPpSORT_DESCEND', 'OPpSORT_INPLACE', 'OPpSORT_REVERSE', 'OPpSORT_INTEGER', 'OPpSORT_NUMERIC'); @{$bits{splice}}{3,2,1,0} = ($bf[4], $bf[4], $bf[4], $bf[4]); @{$bits{split}}{4,3,2} = ('OPpSPLIT_ASSIGN', 'OPpSPLIT_LEX', 'OPpSPLIT_IMPLIM'); @{$bits{sprintf}}{3,2,1,0} = ($bf[4], $bf[4], $bf[4], $bf[4]); $bits{sprotoent}{0} = $bf[0]; $bits{sqrt}{0} = $bf[0]; @{$bits{srand}}{3,2,1,0} = ($bf[4], $bf[4], $bf[4], $bf[4]); $bits{srefgen}{0} = $bf[0]; @{$bits{sselect}}{3,2,1,0} = ($bf[4], $bf[4], $bf[4], $bf[4]); $bits{sservent}{0} = $bf[0]; @{$bits{ssockopt}}{3,2,1,0} = ($bf[4], $bf[4], $bf[4], $bf[4]); $bits{stat}{0} = $bf[0]; @{$bits{stringify}}{3,2,1,0} = ($bf[4], $bf[4], $bf[4], $bf[4]); $bits{study}{0} = $bf[0]; $bits{substcont}{0} = $bf[0]; @{$bits{substr}}{4,2,1,0} = ('OPpSUBSTR_REPL_FIRST', $bf[3], $bf[3], $bf[3]); @{$bits{subtract}}{1,0} = ($bf[1], $bf[1]); @{$bits{symlink}}{3,2,1,0} = ($bf[4], $bf[4], $bf[4], $bf[4]); @{$bits{syscall}}{3,2,1,0} = ($bf[4], $bf[4], $bf[4], $bf[4]); @{$bits{sysopen}}{3,2,1,0} = ($bf[4], $bf[4], $bf[4], $bf[4]); @{$bits{sysread}}{3,2,1,0} = ($bf[4], $bf[4], $bf[4], $bf[4]); @{$bits{sysseek}}{3,2,1,0} = ($bf[4], $bf[4], $bf[4], $bf[4]); @{$bits{system}}{3,2,1,0} = ($bf[4], $bf[4], $bf[4], $bf[4]); @{$bits{syswrite}}{3,2,1,0} = ($bf[4], $bf[4], $bf[4], $bf[4]); @{$bits{tell}}{3,2,1,0} = ($bf[4], $bf[4], $bf[4], $bf[4]); $bits{telldir}{0} = $bf[0]; @{$bits{tie}}{3,2,1,0} = ($bf[4], $bf[4], $bf[4], $bf[4]); $bits{tied}{0} = $bf[0]; @{$bits{truncate}}{3,2,1,0} = ($bf[4], $bf[4], $bf[4], $bf[4]); $bits{uc}{0} = $bf[0]; $bits{ucfirst}{0} = $bf[0]; @{$bits{umask}}{3,2,1,0} = ($bf[4], $bf[4], $bf[4], $bf[4]); $bits{undef}{0} = $bf[0]; @{$bits{unlink}}{3,2,1,0} = ($bf[4], $bf[4], $bf[4], $bf[4]); @{$bits{unpack}}{3,2,1,0} = ($bf[4], $bf[4], $bf[4], $bf[4]); @{$bits{unshift}}{3,2,1,0} = ($bf[4], $bf[4], $bf[4], $bf[4]); $bits{untie}{0} = $bf[0]; @{$bits{utime}}{3,2,1,0} = ($bf[4], $bf[4], $bf[4], $bf[4]); $bits{values}{0} = $bf[0]; @{$bits{vec}}{1,0} = ($bf[1], $bf[1]); @{$bits{waitpid}}{3,2,1,0} = ($bf[4], $bf[4], $bf[4], $bf[4]); @{$bits{warn}}{3,2,1,0} = ($bf[4], $bf[4], $bf[4], $bf[4]); @{$bits{xor}}{1,0} = ($bf[1], $bf[1]); our %defines = ( OPpALLOW_FAKE => 64, OPpARG1_MASK => 1, OPpARG2_MASK => 3, OPpARG3_MASK => 7, OPpARG4_MASK => 15, OPpARGELEM_AV => 2, OPpARGELEM_HV => 4, OPpARGELEM_MASK => 6, OPpARGELEM_SV => 0, OPpASSIGN_BACKWARDS => 64, OPpASSIGN_COMMON_AGG => 16, OPpASSIGN_COMMON_RC1 => 32, OPpASSIGN_COMMON_SCALAR => 64, OPpASSIGN_CV_TO_GV => 128, OPpASSIGN_TRUEBOOL => 4, OPpAVHVSWITCH_MASK => 3, OPpCONCAT_NESTED => 64, OPpCONST_BARE => 64, OPpCONST_ENTERED => 16, OPpCONST_NOVER => 2, OPpCONST_SHORTCIRCUIT => 4, OPpCONST_STRICT => 8, OPpCOREARGS_DEREF1 => 1, OPpCOREARGS_DEREF2 => 2, OPpCOREARGS_PUSHMARK => 128, OPpCOREARGS_SCALARMOD => 64, OPpDEREF => 48, OPpDEREF_AV => 16, OPpDEREF_HV => 32, OPpDEREF_SV => 48, OPpDONT_INIT_GV => 4, OPpEARLY_CV => 32, OPpENTERSUB_AMPER => 8, OPpENTERSUB_DB => 64, OPpENTERSUB_HASTARG => 4, OPpENTERSUB_INARGS => 1, OPpENTERSUB_NOPAREN => 128, OPpEVAL_BYTES => 8, OPpEVAL_COPHH => 16, OPpEVAL_HAS_HH => 2, OPpEVAL_RE_REPARSING => 32, OPpEVAL_UNICODE => 4, OPpEXISTS_SUB => 64, OPpFLIP_LINENUM => 64, OPpFT_ACCESS => 2, OPpFT_AFTER_t => 16, OPpFT_STACKED => 4, OPpFT_STACKING => 8, OPpHINT_STRICT_REFS => 2, OPpHUSH_VMSISH => 32, OPpINDEX_BOOLNEG => 64, OPpITER_DEF => 8, OPpITER_REVERSED => 2, OPpKVSLICE => 32, OPpLIST_GUESSED => 64, OPpLVALUE => 128, OPpLVAL_DEFER => 64, OPpLVAL_INTRO => 128, OPpLVREF_AV => 16, OPpLVREF_CV => 48, OPpLVREF_ELEM => 4, OPpLVREF_HV => 32, OPpLVREF_ITER => 8, OPpLVREF_SV => 0, OPpLVREF_TYPE => 48, OPpMAYBE_LVSUB => 8, OPpMAYBE_TRUEBOOL => 16, OPpMAY_RETURN_CONSTANT => 32, OPpMULTICONCAT_APPEND => 64, OPpMULTICONCAT_FAKE => 32, OPpMULTICONCAT_STRINGIFY => 8, OPpMULTIDEREF_DELETE => 32, OPpMULTIDEREF_EXISTS => 16, OPpOFFBYONE => 128, OPpOPEN_IN_CRLF => 32, OPpOPEN_IN_RAW => 16, OPpOPEN_OUT_CRLF => 128, OPpOPEN_OUT_RAW => 64, OPpOUR_INTRO => 64, OPpPADHV_ISKEYS => 1, OPpPADRANGE_COUNTMASK => 127, OPpPADRANGE_COUNTSHIFT => 7, OPpPAD_STATE => 64, OPpPV_IS_UTF8 => 128, OPpREFCOUNTED => 64, OPpREPEAT_DOLIST => 64, OPpREVERSE_INPLACE => 8, OPpRV2HV_ISKEYS => 1, OPpSLICE => 64, OPpSLICEWARNING => 4, OPpSORT_DESCEND => 16, OPpSORT_INPLACE => 8, OPpSORT_INTEGER => 2, OPpSORT_NUMERIC => 1, OPpSORT_REVERSE => 4, OPpSORT_STABLE => 64, OPpSORT_UNSTABLE => 128, OPpSPLIT_ASSIGN => 16, OPpSPLIT_IMPLIM => 4, OPpSPLIT_LEX => 8, OPpSUBSTR_REPL_FIRST => 16, OPpTARGET_MY => 16, OPpTRANS_COMPLEMENT => 32, OPpTRANS_DELETE => 128, OPpTRANS_FROM_UTF => 1, OPpTRANS_GROWS => 64, OPpTRANS_IDENTICAL => 4, OPpTRANS_SQUASH => 8, OPpTRANS_TO_UTF => 2, OPpTRUEBOOL => 32, ); our %labels = ( OPpALLOW_FAKE => 'FAKE', OPpARGELEM_AV => 'AV', OPpARGELEM_HV => 'HV', OPpARGELEM_SV => 'SV', OPpASSIGN_BACKWARDS => 'BKWARD', OPpASSIGN_COMMON_AGG => 'COM_AGG', OPpASSIGN_COMMON_RC1 => 'COM_RC1', OPpASSIGN_COMMON_SCALAR => 'COM_SCALAR', OPpASSIGN_CV_TO_GV => 'CV2GV', OPpASSIGN_TRUEBOOL => 'BOOL', OPpCONCAT_NESTED => 'NESTED', OPpCONST_BARE => 'BARE', OPpCONST_ENTERED => 'ENTERED', OPpCONST_NOVER => 'NOVER', OPpCONST_SHORTCIRCUIT => 'SHORT', OPpCONST_STRICT => 'STRICT', OPpCOREARGS_DEREF1 => 'DEREF1', OPpCOREARGS_DEREF2 => 'DEREF2', OPpCOREARGS_PUSHMARK => 'MARK', OPpCOREARGS_SCALARMOD => '$MOD', OPpDEREF_AV => 'DREFAV', OPpDEREF_HV => 'DREFHV', OPpDEREF_SV => 'DREFSV', OPpDONT_INIT_GV => 'NOINIT', OPpEARLY_CV => 'EARLYCV', OPpENTERSUB_AMPER => 'AMPER', OPpENTERSUB_DB => 'DBG', OPpENTERSUB_HASTARG => 'TARG', OPpENTERSUB_INARGS => 'INARGS', OPpENTERSUB_NOPAREN => 'NO()', OPpEVAL_BYTES => 'BYTES', OPpEVAL_COPHH => 'COPHH', OPpEVAL_HAS_HH => 'HAS_HH', OPpEVAL_RE_REPARSING => 'REPARSE', OPpEVAL_UNICODE => 'UNI', OPpEXISTS_SUB => 'SUB', OPpFLIP_LINENUM => 'LINENUM', OPpFT_ACCESS => 'FTACCESS', OPpFT_AFTER_t => 'FTAFTERt', OPpFT_STACKED => 'FTSTACKED', OPpFT_STACKING => 'FTSTACKING', OPpHINT_STRICT_REFS => 'STRICT', OPpHUSH_VMSISH => 'HUSH', OPpINDEX_BOOLNEG => 'NEG', OPpITER_DEF => 'DEF', OPpITER_REVERSED => 'REVERSED', OPpKVSLICE => 'KVSLICE', OPpLIST_GUESSED => 'GUESSED', OPpLVALUE => 'LV', OPpLVAL_DEFER => 'LVDEFER', OPpLVAL_INTRO => 'LVINTRO', OPpLVREF_AV => 'AV', OPpLVREF_CV => 'CV', OPpLVREF_ELEM => 'ELEM', OPpLVREF_HV => 'HV', OPpLVREF_ITER => 'ITER', OPpLVREF_SV => 'SV', OPpMAYBE_LVSUB => 'LVSUB', OPpMAYBE_TRUEBOOL => 'BOOL?', OPpMAY_RETURN_CONSTANT => 'CONST', OPpMULTICONCAT_APPEND => 'APPEND', OPpMULTICONCAT_FAKE => 'FAKE', OPpMULTICONCAT_STRINGIFY => 'STRINGIFY', OPpMULTIDEREF_DELETE => 'DELETE', OPpMULTIDEREF_EXISTS => 'EXISTS', OPpOFFBYONE => '+1', OPpOPEN_IN_CRLF => 'INCR', OPpOPEN_IN_RAW => 'INBIN', OPpOPEN_OUT_CRLF => 'OUTCR', OPpOPEN_OUT_RAW => 'OUTBIN', OPpOUR_INTRO => 'OURINTR', OPpPADHV_ISKEYS => 'KEYS', OPpPAD_STATE => 'STATE', OPpPV_IS_UTF8 => 'UTF', OPpREFCOUNTED => 'REFC', OPpREPEAT_DOLIST => 'DOLIST', OPpREVERSE_INPLACE => 'INPLACE', OPpRV2HV_ISKEYS => 'KEYS', OPpSLICE => 'SLICE', OPpSLICEWARNING => 'SLICEWARN', OPpSORT_DESCEND => 'DESC', OPpSORT_INPLACE => 'INPLACE', OPpSORT_INTEGER => 'INT', OPpSORT_NUMERIC => 'NUM', OPpSORT_REVERSE => 'REV', OPpSORT_STABLE => 'STABLE', OPpSORT_UNSTABLE => 'UNSTABLE', OPpSPLIT_ASSIGN => 'ASSIGN', OPpSPLIT_IMPLIM => 'IMPLIM', OPpSPLIT_LEX => 'LEX', OPpSUBSTR_REPL_FIRST => 'REPL1ST', OPpTARGET_MY => 'TARGMY', OPpTRANS_COMPLEMENT => 'COMPL', OPpTRANS_DELETE => 'DEL', OPpTRANS_FROM_UTF => ' 'GROWS', OPpTRANS_IDENTICAL => 'IDENT', OPpTRANS_SQUASH => 'SQUASH', OPpTRANS_TO_UTF => '>UTF', OPpTRUEBOOL => 'BOOL', ); our %ops_using = ( OPpALLOW_FAKE => [qw(rv2gv)], OPpASSIGN_BACKWARDS => [qw(sassign)], OPpASSIGN_COMMON_AGG => [qw(aassign)], OPpCONCAT_NESTED => [qw(concat)], OPpCONST_BARE => [qw(const)], OPpCOREARGS_DEREF1 => [qw(coreargs)], OPpEARLY_CV => [qw(gv)], OPpENTERSUB_AMPER => [qw(entersub rv2cv)], OPpENTERSUB_INARGS => [qw(entersub)], OPpENTERSUB_NOPAREN => [qw(rv2cv)], OPpEVAL_BYTES => [qw(entereval)], OPpEXISTS_SUB => [qw(exists)], OPpFLIP_LINENUM => [qw(flip flop)], OPpFT_ACCESS => [qw(fteexec fteread ftewrite ftrexec ftrread ftrwrite)], OPpFT_AFTER_t => [qw(ftatime ftbinary ftblk ftchr ftctime ftdir fteexec fteowned fteread ftewrite ftfile ftis ftlink ftmtime ftpipe ftrexec ftrowned ftrread ftrwrite ftsgid ftsize ftsock ftsuid ftsvtx fttext fttty ftzero)], OPpHINT_STRICT_REFS => [qw(entersub multideref rv2av rv2cv rv2gv rv2hv rv2sv)], OPpHUSH_VMSISH => [qw(dbstate nextstate)], OPpINDEX_BOOLNEG => [qw(index rindex)], OPpITER_DEF => [qw(enteriter)], OPpITER_REVERSED => [qw(enteriter iter)], OPpKVSLICE => [qw(delete)], OPpLIST_GUESSED => [qw(list)], OPpLVALUE => [qw(leave leaveloop)], OPpLVAL_DEFER => [qw(aelem helem multideref)], OPpLVAL_INTRO => [qw(aelem aslice cond_expr delete enteriter entersub gvsv helem hslice list lvavref lvref lvrefslice multiconcat multideref padav padhv padrange padsv pushmark refassign rv2av rv2gv rv2hv rv2sv split)], OPpLVREF_ELEM => [qw(lvref refassign)], OPpMAYBE_LVSUB => [qw(aassign aelem akeys aslice av2arylen avhvswitch helem hslice keys kvaslice kvhslice multideref padav padhv pos rv2av rv2gv rv2hv substr values vec)], OPpMAYBE_TRUEBOOL => [qw(padhv ref rv2hv)], OPpMULTICONCAT_APPEND => [qw(multiconcat)], OPpMULTIDEREF_DELETE => [qw(multideref)], OPpOFFBYONE => [qw(caller runcv wantarray)], OPpOPEN_IN_CRLF => [qw(backtick open)], OPpOUR_INTRO => [qw(enteriter gvsv rv2av rv2hv rv2sv split)], OPpPADHV_ISKEYS => [qw(padhv)], OPpPAD_STATE => [qw(lvavref lvref padav padhv padsv pushmark refassign)], OPpPV_IS_UTF8 => [qw(dump goto last next redo)], OPpREFCOUNTED => [qw(leave leaveeval leavesub leavesublv leavewrite)], OPpREPEAT_DOLIST => [qw(repeat)], OPpREVERSE_INPLACE => [qw(reverse)], OPpRV2HV_ISKEYS => [qw(rv2hv)], OPpSLICEWARNING => [qw(aslice hslice padav padhv rv2av rv2hv)], OPpSORT_DESCEND => [qw(sort)], OPpSPLIT_ASSIGN => [qw(split)], OPpSUBSTR_REPL_FIRST => [qw(substr)], OPpTARGET_MY => [qw(abs add atan2 chdir chmod chomp chown chr chroot concat cos crypt divide exec exp flock getpgrp getppid getpriority hex i_add i_divide i_modulo i_multiply i_subtract index int kill left_shift length link log mkdir modulo multiconcat multiply nbit_and nbit_or nbit_xor ncomplement oct ord pow push rand rename right_shift rindex rmdir schomp scomplement setpgrp setpriority sin sleep sqrt srand stringify subtract symlink system time unlink unshift utime wait waitpid)], OPpTRANS_COMPLEMENT => [qw(trans transr)], OPpTRUEBOOL => [qw(grepwhile index length padav padhv pos ref rindex rv2av rv2hv subst)], ); $ops_using{OPpASSIGN_COMMON_RC1} = $ops_using{OPpASSIGN_COMMON_AGG}; $ops_using{OPpASSIGN_COMMON_SCALAR} = $ops_using{OPpASSIGN_COMMON_AGG}; $ops_using{OPpASSIGN_CV_TO_GV} = $ops_using{OPpASSIGN_BACKWARDS}; $ops_using{OPpASSIGN_TRUEBOOL} = $ops_using{OPpASSIGN_COMMON_AGG}; $ops_using{OPpCONST_ENTERED} = $ops_using{OPpCONST_BARE}; $ops_using{OPpCONST_NOVER} = $ops_using{OPpCONST_BARE}; $ops_using{OPpCONST_SHORTCIRCUIT} = $ops_using{OPpCONST_BARE}; $ops_using{OPpCONST_STRICT} = $ops_using{OPpCONST_BARE}; $ops_using{OPpCOREARGS_DEREF2} = $ops_using{OPpCOREARGS_DEREF1}; $ops_using{OPpCOREARGS_PUSHMARK} = $ops_using{OPpCOREARGS_DEREF1}; $ops_using{OPpCOREARGS_SCALARMOD} = $ops_using{OPpCOREARGS_DEREF1}; $ops_using{OPpDONT_INIT_GV} = $ops_using{OPpALLOW_FAKE}; $ops_using{OPpENTERSUB_DB} = $ops_using{OPpENTERSUB_AMPER}; $ops_using{OPpENTERSUB_HASTARG} = $ops_using{OPpENTERSUB_AMPER}; $ops_using{OPpEVAL_COPHH} = $ops_using{OPpEVAL_BYTES}; $ops_using{OPpEVAL_HAS_HH} = $ops_using{OPpEVAL_BYTES}; $ops_using{OPpEVAL_RE_REPARSING} = $ops_using{OPpEVAL_BYTES}; $ops_using{OPpEVAL_UNICODE} = $ops_using{OPpEVAL_BYTES}; $ops_using{OPpFT_STACKED} = $ops_using{OPpFT_AFTER_t}; $ops_using{OPpFT_STACKING} = $ops_using{OPpFT_AFTER_t}; $ops_using{OPpLVREF_ITER} = $ops_using{OPpLVREF_ELEM}; $ops_using{OPpMAY_RETURN_CONSTANT} = $ops_using{OPpENTERSUB_NOPAREN}; $ops_using{OPpMULTICONCAT_FAKE} = $ops_using{OPpMULTICONCAT_APPEND}; $ops_using{OPpMULTICONCAT_STRINGIFY} = $ops_using{OPpMULTICONCAT_APPEND}; $ops_using{OPpMULTIDEREF_EXISTS} = $ops_using{OPpMULTIDEREF_DELETE}; $ops_using{OPpOPEN_IN_RAW} = $ops_using{OPpOPEN_IN_CRLF}; $ops_using{OPpOPEN_OUT_CRLF} = $ops_using{OPpOPEN_IN_CRLF}; $ops_using{OPpOPEN_OUT_RAW} = $ops_using{OPpOPEN_IN_CRLF}; $ops_using{OPpSLICE} = $ops_using{OPpKVSLICE}; $ops_using{OPpSORT_INPLACE} = $ops_using{OPpSORT_DESCEND}; $ops_using{OPpSORT_INTEGER} = $ops_using{OPpSORT_DESCEND}; $ops_using{OPpSORT_NUMERIC} = $ops_using{OPpSORT_DESCEND}; $ops_using{OPpSORT_REVERSE} = $ops_using{OPpSORT_DESCEND}; $ops_using{OPpSORT_STABLE} = $ops_using{OPpSORT_DESCEND}; $ops_using{OPpSORT_UNSTABLE} = $ops_using{OPpSORT_DESCEND}; $ops_using{OPpSPLIT_IMPLIM} = $ops_using{OPpSPLIT_ASSIGN}; $ops_using{OPpSPLIT_LEX} = $ops_using{OPpSPLIT_ASSIGN}; $ops_using{OPpTRANS_DELETE} = $ops_using{OPpTRANS_COMPLEMENT}; $ops_using{OPpTRANS_FROM_UTF} = $ops_using{OPpTRANS_COMPLEMENT}; $ops_using{OPpTRANS_GROWS} = $ops_using{OPpTRANS_COMPLEMENT}; $ops_using{OPpTRANS_IDENTICAL} = $ops_using{OPpTRANS_COMPLEMENT}; $ops_using{OPpTRANS_SQUASH} = $ops_using{OPpTRANS_COMPLEMENT}; $ops_using{OPpTRANS_TO_UTF} = $ops_using{OPpTRANS_COMPLEMENT}; # ex: set ro: package B::Debug; our $VERSION = '1.26'; BEGIN { if ($] >= 5.027001) { require deprecate; import deprecate; } } use strict; require 5.006; use B qw(peekop walkoptree walkoptree_exec main_start main_root cstring sv_undef SVf_NOK SVf_IOK); use Config; my (@optype, @specialsv_name); require B; if ($] < 5.009) { require B::Asmdata; B::Asmdata->import (qw(@optype @specialsv_name)); } else { B->import (qw(@optype @specialsv_name)); } if ($] < 5.006002) { eval q|sub B::GV::SAFENAME { my $name = (shift())->NAME; # The regex below corresponds to the isCONTROLVAR macro from toke.c $name =~ s/^([\cA-\cZ\c\\c[\c]\c?\c_\c^])/"^".chr(64 ^ ord($1))/e; return $name; }|; } my ($have_B_Flags, $have_B_Flags_extra); if (!$ENV{PERL_CORE}){ # avoid CORE test crashes eval { require B::Flags and $have_B_Flags++ }; $have_B_Flags_extra++ if $have_B_Flags and $B::Flags::VERSION gt '0.03'; } my %done_gv; sub _printop { my $op = shift; my $addr = ${$op} ? $op->ppaddr : ''; $addr =~ s/^PL_ppaddr// if $addr; if (${$op}) { return sprintf "0x%08x %6s %s", ${$op}, B::class($op), $addr; } else { return sprintf "0x%x %6s %s", ${$op}, '', $addr; } } sub B::OP::debug { my ($op) = @_; printf <<'EOT', B::class($op), $$op, _printop($op), _printop($op->next), _printop($op->sibling), $op->targ, $op->type, $op->name; %s (0x%lx) op_ppaddr %s op_next %s op_sibling %s op_targ %d op_type %d %s EOT if ($] > 5.009) { printf <<'EOT', $op->opt; op_opt %d EOT } else { printf <<'EOT', $op->seq; op_seq %d EOT } if ($have_B_Flags) { printf <<'EOT', $op->flags, $op->flagspv, $op->private, $op->privatepv; op_flags %u %s op_private %u %s EOT } else { printf <<'EOT', $op->flags, $op->private; op_flags %u op_private %u EOT } if ($op->can('rettype')) { printf <<'EOT', $op->rettype; op_rettype %u EOT } } sub B::UNOP::debug { my ($op) = @_; $op->B::OP::debug(); printf "\top_first\t%s\n", _printop($op->first); } sub B::BINOP::debug { my ($op) = @_; $op->B::UNOP::debug(); printf "\top_last \t%s\n", _printop($op->last); } sub B::LOOP::debug { my ($op) = @_; $op->B::BINOP::debug(); printf <<'EOT', _printop($op->redoop), _printop($op->nextop), _printop($op->lastop); op_redoop %s op_nextop %s op_lastop %s EOT } sub B::LOGOP::debug { my ($op) = @_; $op->B::UNOP::debug(); printf "\top_other\t%s\n", _printop($op->other); } sub B::LISTOP::debug { my ($op) = @_; $op->B::BINOP::debug(); printf "\top_children\t%d\n", $op->children; } sub B::PMOP::debug { my ($op) = @_; $op->B::LISTOP::debug(); printf "\top_pmreplroot\t0x%x\n", $] < 5.008 ? ${$op->pmreplroot} : $op->pmreplroot; printf "\top_pmreplstart\t0x%x\n", ${$op->pmreplstart}; printf "\top_pmnext\t0x%x\n", ${$op->pmnext} if $] < 5.009005; if ($Config{'useithreads'}) { printf "\top_pmstashpv\t%s\n", cstring($op->pmstashpv); printf "\top_pmoffset\t%d\n", $op->pmoffset; } else { printf "\top_pmstash\t%s\n", cstring($op->pmstash); } printf "\top_precomp\t%s\n", cstring($op->precomp); printf "\top_pmflags\t0x%x\n", $op->pmflags; printf "\top_reflags\t0x%x\n", $op->reflags if $] >= 5.009; printf "\top_pmpermflags\t0x%x\n", $op->pmpermflags if $] < 5.009; printf "\top_pmdynflags\t0x%x\n", $op->pmdynflags if $] < 5.009; $op->pmreplroot->debug if $] < 5.008; } sub B::COP::debug { my ($op) = @_; $op->B::OP::debug(); my $warnings = ref $op->warnings ? ${$op->warnings} : 0; printf <<'EOT', $op->label, $op->stashpv, $op->file, $op->cop_seq, $op->arybase, $op->line, $warnings; cop_label "%s" cop_stashpv "%s" cop_file "%s" cop_seq %d cop_arybase %d cop_line %d cop_warnings 0x%x EOT if ($] > 5.008 and $] < 5.011) { my $cop_io = B::class($op->io) eq 'SPECIAL' ? '' : $op->io->as_string; printf(" cop_io %s\n", cstring($cop_io)); } } sub B::SVOP::debug { my ($op) = @_; $op->B::OP::debug(); printf "\top_sv\t\t0x%x\n", ${$op->sv}; $op->sv->debug; } sub B::METHOP::debug { my ($op) = @_; $op->B::OP::debug(); if (${$op->first}) { printf "\top_first\t0x%x\n", ${$op->first}; $op->first->debug; } else { printf "\top_meth_sv\t0x%x\n", ${$op->meth_sv}; $op->meth_sv->debug; } } sub B::UNOP_AUX::debug { my ($op) = @_; $op->B::OP::debug(); # string and perl5 aux_list needs the cv # cperl has aux, Concise,-debug leaves it empty if ($op->can('aux')) { printf "\top_aux\t%s\n", cstring($op->aux); } } sub B::PVOP::debug { my ($op) = @_; $op->B::OP::debug(); printf "\top_pv\t\t%s\n", cstring($op->pv); } sub B::PADOP::debug { my ($op) = @_; $op->B::OP::debug(); printf "\top_padix\t%ld\n", $op->padix; } sub B::NULL::debug { my ($sv) = @_; if ($$sv == ${sv_undef()}) { print "&sv_undef\n"; } else { printf "NULL (0x%x)\n", $$sv; } } sub B::SV::debug { my ($sv) = @_; if (!$$sv) { print B::class($sv), " = NULL\n"; return; } printf <<'EOT', B::class($sv), $$sv, $sv->REFCNT; %s (0x%x) REFCNT %d EOT printf "\tFLAGS\t\t0x%x", $sv->FLAGS; if ($have_B_Flags) { printf "\t%s", $have_B_Flags_extra ? $sv->flagspv(0) : $sv->flagspv; } print "\n"; } sub B::RV::debug { my ($rv) = @_; B::SV::debug($rv); printf <<'EOT', ${$rv->RV}; RV 0x%x EOT $rv->RV->debug; } sub B::PV::debug { my ($sv) = @_; $sv->B::SV::debug(); my $pv = $sv->PV(); printf <<'EOT', cstring($pv), $sv->CUR, $sv->LEN; xpv_pv %s xpv_cur %d xpv_len %d EOT } sub B::IV::debug { my ($sv) = @_; $sv->B::SV::debug(); printf "\txiv_iv\t\t%d\n", $sv->IV if $sv->FLAGS & SVf_IOK; } sub B::NV::debug { my ($sv) = @_; $sv->B::IV::debug(); printf "\txnv_nv\t\t%s\n", $sv->NV if $sv->FLAGS & SVf_NOK; } sub B::PVIV::debug { my ($sv) = @_; $sv->B::PV::debug(); printf "\txiv_iv\t\t%d\n", $sv->IV if $sv->FLAGS & SVf_IOK; } sub B::PVNV::debug { my ($sv) = @_; $sv->B::PVIV::debug(); printf "\txnv_nv\t\t%s\n", $sv->NV if $sv->FLAGS & SVf_NOK; } sub B::PVLV::debug { my ($sv) = @_; $sv->B::PVNV::debug(); printf "\txlv_targoff\t%d\n", $sv->TARGOFF; printf "\txlv_targlen\t%u\n", $sv->TARGLEN; printf "\txlv_type\t%s\n", cstring(chr($sv->TYPE)); } sub B::BM::debug { my ($sv) = @_; $sv->B::PVNV::debug(); printf "\txbm_useful\t%d\n", $sv->USEFUL; printf "\txbm_previous\t%u\n", $sv->PREVIOUS; printf "\txbm_rare\t%s\n", cstring(chr($sv->RARE)); } sub B::CV::debug { my ($cv) = @_; $cv->B::PVNV::debug(); my $stash = $cv->STASH; my $start = $cv->START; my $root = $cv->ROOT; my $padlist = $cv->PADLIST; my $file = $cv->FILE; my $gv; printf <<'EOT', $$stash, $$start, $$root; STASH 0x%x START 0x%x ROOT 0x%x EOT if ($cv->can('NAME_HEK') && $cv->NAME_HEK) { printf("\tNAME\t%%s\n", $cv->NAME_HEK); } elsif ( $]>5.017 && ($cv->FLAGS & 0x40000)) { #lexsub printf("\tNAME\t%%s\n", $cv->NAME_HEK); } else { $gv = $cv->GV; printf("\tGV\t%0x%x\t%s\n", $$gv, $gv->SAFENAME); } printf <<'EOT', $file, $cv->DEPTH, $padlist, ${$cv->OUTSIDE}; FILE %s DEPTH %d PADLIST 0x%x OUTSIDE 0x%x EOT printf("\tOUTSIDE_SEQ\t%d\n", $cv->OUTSIDE_SEQ) if $] > 5.007; if ($have_B_Flags) { my $SVt_PVCV = $] < 5.010 ? 12 : 13; printf("\tCvFLAGS\t0x%x\t%s\n", $cv->CvFLAGS, $have_B_Flags_extra ? $cv->flagspv($SVt_PVCV) : $cv->flagspv); } else { printf("\tCvFLAGS\t0x%x\n", $cv->CvFLAGS); } printf("\tSIGOP\t0x%x\n", $cv->SIGOP) if $cv->can('SIGOP'); $start->debug if $start; $root->debug if $root; $gv->debug if $gv; $padlist->debug if $padlist; } sub B::AV::debug { my ($av) = @_; $av->B::SV::debug; _array_debug($av); } sub _array_debug { my ($av) = @_; # tied arrays may leave out FETCHSIZE my (@array) = eval { $av->ARRAY; }; print "\tARRAY\t\t(", join(", ", map("0x" . $$_, @array)), ")\n"; my $fill = eval { scalar(@array) }; if ($Config{'useithreads'} && B::class($av) ne 'PADLIST') { printf <<'EOT', $fill, $av->MAX, $av->OFF; FILL %d MAX %d OFF %d EOT } else { printf <<'EOT', $fill, $av->MAX; FILL %d MAX %d EOT } if ($] < 5.009) { if ($have_B_Flags) { printf("\tAvFLAGS\t0x%x\t%s\n", $av->AvFLAGS, $have_B_Flags_extra ? $av->flagspv(10) : $av->flagspv); } else { printf("\tAvFLAGS\t0x%x\n", $av->AvFLAGS); } } } sub B::GV::debug { my ($gv) = @_; if ($done_gv{$$gv}++) { printf "GV %s::%s\n", $gv->STASH->NAME, $gv->SAFENAME; return; } my $sv = $gv->SV; my $av = $gv->AV; my $cv = $gv->CV; $gv->B::SV::debug; printf <<'EOT', $gv->SAFENAME, $gv->STASH->NAME, $gv->STASH, $$sv, $gv->GvREFCNT, $gv->FORM, $$av, ${$gv->HV}, ${$gv->EGV}, $$cv, $gv->CVGEN, $gv->LINE, $gv->FILE, $gv->GvFLAGS; NAME %s STASH %s (0x%x) SV 0x%x GvREFCNT %d FORM 0x%x AV 0x%x HV 0x%x EGV 0x%x CV 0x%x CVGEN %d LINE %d FILE %s EOT if ($have_B_Flags) { my $SVt_PVGV = $] < 5.010 ? 13 : 9; printf("\tGvFLAGS\t0x%x\t%s\n", $gv->GvFLAGS, $have_B_Flags_extra ? $gv->flagspv($SVt_PVGV) : $gv->flagspv); } else { printf("\tGvFLAGS\t0x%x\n", $gv->GvFLAGS); } $sv->debug if $sv; $av->debug if $av; $cv->debug if $cv; } sub B::SPECIAL::debug { my $sv = shift; my $i = ref $sv ? $$sv : 0; print defined $specialsv_name[$i] ? $specialsv_name[$i] : "", "\n"; } sub B::PADLIST::debug { my ($padlist) = @_; printf <<'EOT', B::class($padlist), $$padlist, $padlist->REFCNT; %s (0x%x) REFCNT %d EOT _array_debug($padlist); } sub compile { my $order = shift; B::clearsym(); $DB::single = 1 if defined &DB::DB; if ($order && $order eq "exec") { return sub { walkoptree_exec(main_start, "debug") } } else { return sub { walkoptree(main_root, "debug") } } } 1; __END__ # Net::SMTP.pm # # Copyright (C) 1995-2004 Graham Barr. All rights reserved. # Copyright (C) 2013-2016 Steve Hay. All rights reserved. # This module is free software; you can redistribute it and/or modify it under # the same terms as Perl itself, i.e. under the terms of either the GNU General # Public License or the Artistic License, as specified in the F file. package Net::SMTP; use 5.008001; use strict; use warnings; use Carp; use IO::Socket; use Net::Cmd; use Net::Config; use Socket; our $VERSION = "3.11"; # Code for detecting if we can use SSL my $ssl_class = eval { require IO::Socket::SSL; # first version with default CA on most platforms no warnings 'numeric'; IO::Socket::SSL->VERSION(2.007); } && 'IO::Socket::SSL'; my $nossl_warn = !$ssl_class && 'To use SSL please install IO::Socket::SSL with version>=2.007'; # Code for detecting if we can use IPv6 my $family_key = 'Domain'; my $inet6_class = eval { require IO::Socket::IP; no warnings 'numeric'; IO::Socket::IP->VERSION(0.25) || die; $family_key = 'Family'; } && 'IO::Socket::IP' || eval { require IO::Socket::INET6; no warnings 'numeric'; IO::Socket::INET6->VERSION(2.62); } && 'IO::Socket::INET6'; sub can_ssl { $ssl_class }; sub can_inet6 { $inet6_class }; our @ISA = ('Net::Cmd', $inet6_class || 'IO::Socket::INET'); sub new { my $self = shift; my $type = ref($self) || $self; my ($host, %arg); if (@_ % 2) { $host = shift; %arg = @_; } else { %arg = @_; $host = delete $arg{Host}; } if ($arg{SSL}) { # SSL from start die $nossl_warn if !$ssl_class; $arg{Port} ||= 465; } my $hosts = defined $host ? $host : $NetConfig{smtp_hosts}; my $obj; $arg{Timeout} = 120 if ! defined $arg{Timeout}; foreach my $h (@{ref($hosts) ? $hosts : [$hosts]}) { $obj = $type->SUPER::new( PeerAddr => ($host = $h), PeerPort => $arg{Port} || 'smtp(25)', LocalAddr => $arg{LocalAddr}, LocalPort => $arg{LocalPort}, $family_key => $arg{Domain} || $arg{Family}, Proto => 'tcp', Timeout => $arg{Timeout} ) and last; } return unless defined $obj; ${*$obj}{'net_smtp_arg'} = \%arg; ${*$obj}{'net_smtp_host'} = $host; if ($arg{SSL}) { Net::SMTP::_SSL->start_SSL($obj,%arg) or return; } $obj->autoflush(1); $obj->debug(exists $arg{Debug} ? $arg{Debug} : undef); unless ($obj->response() == CMD_OK) { my $err = ref($obj) . ": " . $obj->code . " " . $obj->message; $obj->close(); $@ = $err; return; } ${*$obj}{'net_smtp_exact_addr'} = $arg{ExactAddresses}; (${*$obj}{'net_smtp_banner'}) = $obj->message; (${*$obj}{'net_smtp_domain'}) = $obj->message =~ /\A\s*(\S+)/; if (!exists $arg{SendHello} || $arg{SendHello}) { unless ($obj->hello($arg{Hello} || "")) { my $err = ref($obj) . ": " . $obj->code . " " . $obj->message; $obj->close(); $@ = $err; return; } } $obj; } sub host { my $me = shift; ${*$me}{'net_smtp_host'}; } ## ## User interface methods ## sub banner { my $me = shift; return ${*$me}{'net_smtp_banner'} || undef; } sub domain { my $me = shift; return ${*$me}{'net_smtp_domain'} || undef; } sub etrn { my $self = shift; defined($self->supports('ETRN', 500, ["Command unknown: 'ETRN'"])) && $self->_ETRN(@_); } sub auth { my ($self, $username, $password) = @_; eval { require MIME::Base64; require Authen::SASL; } or $self->set_status(500, ["Need MIME::Base64 and Authen::SASL todo auth"]), return 0; my $mechanisms = $self->supports('AUTH', 500, ["Command unknown: 'AUTH'"]); return unless defined $mechanisms; my $sasl; if (ref($username) and UNIVERSAL::isa($username, 'Authen::SASL')) { $sasl = $username; my $requested_mechanisms = $sasl->mechanism(); if (! defined($requested_mechanisms) || $requested_mechanisms eq '') { $sasl->mechanism($mechanisms); } } else { die "auth(username, password)" if not length $username; $sasl = Authen::SASL->new( mechanism => $mechanisms, callback => { user => $username, pass => $password, authname => $username, }, debug => $self->debug ); } my $client; my $str; do { if ($client) { # $client mechanism failed, so we need to exclude this mechanism from list my $failed_mechanism = $client->mechanism; return unless defined $failed_mechanism; $self->debug_text("Auth mechanism failed: $failed_mechanism") if $self->debug; $mechanisms =~ s/\b\Q$failed_mechanism\E\b//; return unless $mechanisms =~ /\S/; $sasl->mechanism($mechanisms); } # We should probably allow the user to pass the host, but I don't # currently know and SASL mechanisms that are used by smtp that need it $client = $sasl->client_new('smtp', ${*$self}{'net_smtp_host'}, 0); $str = $client->client_start; } while (!defined $str); # We don't support sasl mechanisms that encrypt the socket traffic. # todo that we would really need to change the ISA hierarchy # so we don't inherit from IO::Socket, but instead hold it in an attribute my @cmd = ("AUTH", $client->mechanism); my $code; push @cmd, MIME::Base64::encode_base64($str, '') if defined $str and length $str; while (($code = $self->command(@cmd)->response()) == CMD_MORE) { my $str2 = MIME::Base64::decode_base64(($self->message)[0]); $self->debug_print(0, "(decoded) " . $str2 . "\n") if $self->debug; $str = $client->client_step($str2); @cmd = ( MIME::Base64::encode_base64($str, '') ); $self->debug_print(1, "(decoded) " . $str . "\n") if $self->debug; } $code == CMD_OK; } sub hello { my $me = shift; my $domain = shift || "localhost.localdomain"; my $ok = $me->_EHLO($domain); my @msg = $me->message; if ($ok) { my $h = ${*$me}{'net_smtp_esmtp'} = {}; foreach my $ln (@msg) { $h->{uc $1} = $2 if $ln =~ /([-\w]+)\b[= \t]*([^\n]*)/; } } elsif ($me->status == CMD_ERROR) { @msg = $me->message if $ok = $me->_HELO($domain); } return unless $ok; ${*$me}{net_smtp_hello_domain} = $domain; $msg[0] =~ /\A\s*(\S+)/; return ($1 || " "); } sub starttls { my $self = shift; $ssl_class or die $nossl_warn; $self->_STARTTLS or return; Net::SMTP::_SSL->start_SSL($self, %{ ${*$self}{'net_smtp_arg'} }, # (ssl) args given in new @_ # more (ssl) args ) or return; # another hello after starttls to read new ESMTP capabilities return $self->hello(${*$self}{net_smtp_hello_domain}); } sub supports { my $self = shift; my $cmd = uc shift; return ${*$self}{'net_smtp_esmtp'}->{$cmd} if exists ${*$self}{'net_smtp_esmtp'}->{$cmd}; $self->set_status(@_) if @_; return; } sub _addr { my $self = shift; my $addr = shift; $addr = "" unless defined $addr; if (${*$self}{'net_smtp_exact_addr'}) { return $1 if $addr =~ /^\s*(<.*>)\s*$/s; } else { return $1 if $addr =~ /(<[^>]*>)/; $addr =~ s/^\s+|\s+$//sg; } "<$addr>"; } sub mail { my $me = shift; my $addr = _addr($me, shift); my $opts = ""; if (@_) { my %opt = @_; my ($k, $v); if (exists ${*$me}{'net_smtp_esmtp'}) { my $esmtp = ${*$me}{'net_smtp_esmtp'}; if (defined($v = delete $opt{Size})) { if (exists $esmtp->{SIZE}) { $opts .= sprintf " SIZE=%d", $v + 0; } else { carp 'Net::SMTP::mail: SIZE option not supported by host'; } } if (defined($v = delete $opt{Return})) { if (exists $esmtp->{DSN}) { $opts .= " RET=" . ((uc($v) eq "FULL") ? "FULL" : "HDRS"); } else { carp 'Net::SMTP::mail: DSN option not supported by host'; } } if (defined($v = delete $opt{Bits})) { if ($v eq "8") { if (exists $esmtp->{'8BITMIME'}) { $opts .= " BODY=8BITMIME"; } else { carp 'Net::SMTP::mail: 8BITMIME option not supported by host'; } } elsif ($v eq "binary") { if (exists $esmtp->{'BINARYMIME'} && exists $esmtp->{'CHUNKING'}) { $opts .= " BODY=BINARYMIME"; ${*$me}{'net_smtp_chunking'} = 1; } else { carp 'Net::SMTP::mail: BINARYMIME option not supported by host'; } } elsif (exists $esmtp->{'8BITMIME'} or exists $esmtp->{'BINARYMIME'}) { $opts .= " BODY=7BIT"; } else { carp 'Net::SMTP::mail: 8BITMIME and BINARYMIME options not supported by host'; } } if (defined($v = delete $opt{Transaction})) { if (exists $esmtp->{CHECKPOINT}) { $opts .= " TRANSID=" . _addr($me, $v); } else { carp 'Net::SMTP::mail: CHECKPOINT option not supported by host'; } } if (defined($v = delete $opt{Envelope})) { if (exists $esmtp->{DSN}) { $v =~ s/([^\041-\176]|=|\+)/sprintf "+%02X", ord($1)/sge; $opts .= " ENVID=$v"; } else { carp 'Net::SMTP::mail: DSN option not supported by host'; } } if (defined($v = delete $opt{ENVID})) { # expected to be in a format as required by RFC 3461, xtext-encoded if (exists $esmtp->{DSN}) { $opts .= " ENVID=$v"; } else { carp 'Net::SMTP::mail: DSN option not supported by host'; } } if (defined($v = delete $opt{AUTH})) { # expected to be in a format as required by RFC 2554, # rfc2821-quoted and xtext-encoded, or <> if (exists $esmtp->{AUTH}) { $v = '<>' if !defined($v) || $v eq ''; $opts .= " AUTH=$v"; } else { carp 'Net::SMTP::mail: AUTH option not supported by host'; } } if (defined($v = delete $opt{XVERP})) { if (exists $esmtp->{'XVERP'}) { $opts .= " XVERP"; } else { carp 'Net::SMTP::mail: XVERP option not supported by host'; } } carp 'Net::SMTP::recipient: unknown option(s) ' . join(" ", keys %opt) . ' - ignored' if scalar keys %opt; } else { carp 'Net::SMTP::mail: ESMTP not supported by host - options discarded :-('; } } $me->_MAIL("FROM:" . $addr . $opts); } sub send { my $me = shift; $me->_SEND("FROM:" . _addr($me, $_[0])) } sub send_or_mail { my $me = shift; $me->_SOML("FROM:" . _addr($me, $_[0])) } sub send_and_mail { my $me = shift; $me->_SAML("FROM:" . _addr($me, $_[0])) } sub reset { my $me = shift; $me->dataend() if (exists ${*$me}{'net_smtp_lastch'}); $me->_RSET(); } sub recipient { my $smtp = shift; my $opts = ""; my $skip_bad = 0; if (@_ && ref($_[-1])) { my %opt = %{pop(@_)}; my $v; $skip_bad = delete $opt{'SkipBad'}; if (exists ${*$smtp}{'net_smtp_esmtp'}) { my $esmtp = ${*$smtp}{'net_smtp_esmtp'}; if (defined($v = delete $opt{Notify})) { if (exists $esmtp->{DSN}) { $opts .= " NOTIFY=" . join(",", map { uc $_ } @$v); } else { carp 'Net::SMTP::recipient: DSN option not supported by host'; } } if (defined($v = delete $opt{ORcpt})) { if (exists $esmtp->{DSN}) { $opts .= " ORCPT=" . $v; } else { carp 'Net::SMTP::recipient: DSN option not supported by host'; } } carp 'Net::SMTP::recipient: unknown option(s) ' . join(" ", keys %opt) . ' - ignored' if scalar keys %opt; } elsif (%opt) { carp 'Net::SMTP::recipient: ESMTP not supported by host - options discarded :-('; } } my @ok; foreach my $addr (@_) { if ($smtp->_RCPT("TO:" . _addr($smtp, $addr) . $opts)) { push(@ok, $addr) if $skip_bad; } elsif (!$skip_bad) { return 0; } } return $skip_bad ? @ok : 1; } BEGIN { *to = \&recipient; *cc = \&recipient; *bcc = \&recipient; } sub data { my $me = shift; if (exists ${*$me}{'net_smtp_chunking'}) { carp 'Net::SMTP::data: CHUNKING extension in use, must call bdat instead'; } else { my $ok = $me->_DATA() && $me->datasend(@_); $ok && @_ ? $me->dataend : $ok; } } sub bdat { my $me = shift; if (exists ${*$me}{'net_smtp_chunking'}) { my $data = shift; $me->_BDAT(length $data) && $me->rawdatasend($data) && $me->response() == CMD_OK; } else { carp 'Net::SMTP::bdat: CHUNKING extension is not in use, call data instead'; } } sub bdatlast { my $me = shift; if (exists ${*$me}{'net_smtp_chunking'}) { my $data = shift; $me->_BDAT(length $data, "LAST") && $me->rawdatasend($data) && $me->response() == CMD_OK; } else { carp 'Net::SMTP::bdat: CHUNKING extension is not in use, call data instead'; } } sub datafh { my $me = shift; return unless $me->_DATA(); return $me->tied_fh; } sub expand { my $me = shift; $me->_EXPN(@_) ? ($me->message) : (); } sub verify { shift->_VRFY(@_) } sub help { my $me = shift; $me->_HELP(@_) ? scalar $me->message : undef; } sub quit { my $me = shift; $me->_QUIT; $me->close; } sub DESTROY { # ignore } ## ## RFC821 commands ## sub _EHLO { shift->command("EHLO", @_)->response() == CMD_OK } sub _HELO { shift->command("HELO", @_)->response() == CMD_OK } sub _MAIL { shift->command("MAIL", @_)->response() == CMD_OK } sub _RCPT { shift->command("RCPT", @_)->response() == CMD_OK } sub _SEND { shift->command("SEND", @_)->response() == CMD_OK } sub _SAML { shift->command("SAML", @_)->response() == CMD_OK } sub _SOML { shift->command("SOML", @_)->response() == CMD_OK } sub _VRFY { shift->command("VRFY", @_)->response() == CMD_OK } sub _EXPN { shift->command("EXPN", @_)->response() == CMD_OK } sub _HELP { shift->command("HELP", @_)->response() == CMD_OK } sub _RSET { shift->command("RSET")->response() == CMD_OK } sub _NOOP { shift->command("NOOP")->response() == CMD_OK } sub _QUIT { shift->command("QUIT")->response() == CMD_OK } sub _DATA { shift->command("DATA")->response() == CMD_MORE } sub _BDAT { shift->command("BDAT", @_) } sub _TURN { shift->unsupported(@_); } sub _ETRN { shift->command("ETRN", @_)->response() == CMD_OK } sub _AUTH { shift->command("AUTH", @_)->response() == CMD_OK } sub _STARTTLS { shift->command("STARTTLS")->response() == CMD_OK } { package Net::SMTP::_SSL; our @ISA = ( $ssl_class ? ($ssl_class):(), 'Net::SMTP' ); sub starttls { die "SMTP connection is already in SSL mode" } sub start_SSL { my ($class,$smtp,%arg) = @_; delete @arg{ grep { !m{^SSL_} } keys %arg }; ( $arg{SSL_verifycn_name} ||= $smtp->host ) =~s{(?can_client_sni; $arg{SSL_verifycn_scheme} ||= 'smtp'; my $ok = $class->SUPER::start_SSL($smtp,%arg); $@ = $ssl_class->errstr if !$ok; return $ok; } } 1; __END__ package Net::hostent; use strict; use 5.006_001; our $VERSION = '1.02'; our (@EXPORT, @EXPORT_OK, %EXPORT_TAGS); our ( $h_name, @h_aliases, $h_addrtype, $h_length, @h_addr_list, $h_addr ); BEGIN { use Exporter (); @EXPORT = qw(gethostbyname gethostbyaddr gethost); @EXPORT_OK = qw( $h_name @h_aliases $h_addrtype $h_length @h_addr_list $h_addr ); %EXPORT_TAGS = ( FIELDS => [ @EXPORT_OK, @EXPORT ] ); } # Class::Struct forbids use of @ISA sub import { goto &Exporter::import } use Class::Struct qw(struct); struct 'Net::hostent' => [ name => '$', aliases => '@', addrtype => '$', 'length' => '$', addr_list => '@', ]; sub addr { shift->addr_list->[0] } sub populate (@) { return unless @_; my $hob = new(); $h_name = $hob->[0] = $_[0]; @h_aliases = @{ $hob->[1] } = split ' ', $_[1]; $h_addrtype = $hob->[2] = $_[2]; $h_length = $hob->[3] = $_[3]; $h_addr = $_[4]; @h_addr_list = @{ $hob->[4] } = @_[ (4 .. $#_) ]; return $hob; } sub gethostbyname ($) { populate(CORE::gethostbyname(shift)) } sub gethostbyaddr ($;$) { my ($addr, $addrtype); $addr = shift; require Socket unless @_; $addrtype = @_ ? shift : Socket::AF_INET(); populate(CORE::gethostbyaddr($addr, $addrtype)) } sub gethost($) { if ($_[0] =~ /^\d+(?:\.\d+(?:\.\d+(?:\.\d+)?)?)?$/) { require Socket; &gethostbyaddr(Socket::inet_aton(shift)); } else { &gethostbyname; } } 1; __END__ # Net::Domain.pm # # Copyright (C) 1995-1998 Graham Barr. All rights reserved. # Copyright (C) 2013-2014 Steve Hay. All rights reserved. # This module is free software; you can redistribute it and/or modify it under # the same terms as Perl itself, i.e. under the terms of either the GNU General # Public License or the Artistic License, as specified in the F file. package Net::Domain; use 5.008001; use strict; use warnings; use Carp; use Exporter; use Net::Config; our @ISA = qw(Exporter); our @EXPORT_OK = qw(hostname hostdomain hostfqdn domainname); our $VERSION = "3.11"; my ($host, $domain, $fqdn) = (undef, undef, undef); # Try every conceivable way to get hostname. sub _hostname { # we already know it return $host if (defined $host); if ($^O eq 'MSWin32') { require Socket; my ($name, $alias, $type, $len, @addr) = gethostbyname($ENV{'COMPUTERNAME'} || 'localhost'); while (@addr) { my $a = shift(@addr); $host = gethostbyaddr($a, Socket::AF_INET()); last if defined $host; } if (defined($host) && index($host, '.') > 0) { $fqdn = $host; ($host, $domain) = $fqdn =~ /^([^.]+)\.(.*)$/; } return $host; } elsif ($^O eq 'MacOS') { chomp($host = `hostname`); } elsif ($^O eq 'VMS') { ## multiple varieties of net s/w makes this hard $host = $ENV{'UCX$INET_HOST'} if defined($ENV{'UCX$INET_HOST'}); $host = $ENV{'MULTINET_HOST_NAME'} if defined($ENV{'MULTINET_HOST_NAME'}); if (index($host, '.') > 0) { $fqdn = $host; ($host, $domain) = $fqdn =~ /^([^.]+)\.(.*)$/; } return $host; } else { local $SIG{'__DIE__'}; # syscall is preferred since it avoids tainting problems eval { my $tmp = "\0" x 256; ## preload scalar eval { package main; require "syscall.ph"; ## no critic (Modules::RequireBarewordIncludes) defined(&main::SYS_gethostname); } || eval { package main; require "sys/syscall.ph"; ## no critic (Modules::RequireBarewordIncludes) defined(&main::SYS_gethostname); } and $host = (syscall(&main::SYS_gethostname, $tmp, 256) == 0) ? $tmp : undef; } # POSIX || eval { require POSIX; $host = (POSIX::uname())[1]; } # trusty old hostname command || eval { chop($host = `(hostname) 2>/dev/null`); # BSD'ish } # sysV/POSIX uname command (may truncate) || eval { chop($host = `uname -n 2>/dev/null`); ## SYSV'ish && POSIX'ish } # Apollo pre-SR10 || eval { $host = (split(/[:. ]/, `/com/host`, 6))[0]; } || eval { $host = ""; }; } # remove garbage $host =~ s/[\0\r\n]+//go; $host =~ s/(\A\.+|\.+\Z)//go; $host =~ s/\.\.+/\./go; $host; } sub _hostdomain { # we already know it return $domain if (defined $domain); local $SIG{'__DIE__'}; return $domain = $NetConfig{'inet_domain'} if defined $NetConfig{'inet_domain'}; # try looking in /etc/resolv.conf # putting this here and assuming that it is correct, eliminates # calls to gethostbyname, and therefore DNS lookups. This helps # those on dialup systems. local ($_); if (open(my $res, '<', "/etc/resolv.conf")) { while (<$res>) { $domain = $1 if (/\A\s*(?:domain|search)\s+(\S+)/); } close($res); return $domain if (defined $domain); } # just try hostname and system calls my $host = _hostname(); my (@hosts); @hosts = ($host, "localhost"); unless (defined($host) && $host =~ /\./) { my $dom = undef; eval { my $tmp = "\0" x 256; ## preload scalar eval { package main; require "syscall.ph"; ## no critic (Modules::RequireBarewordIncludes) } || eval { package main; require "sys/syscall.ph"; ## no critic (Modules::RequireBarewordIncludes) } and $dom = (syscall(&main::SYS_getdomainname, $tmp, 256) == 0) ? $tmp : undef; }; if ($^O eq 'VMS') { $dom ||= $ENV{'TCPIP$INET_DOMAIN'} || $ENV{'UCX$INET_DOMAIN'}; } chop($dom = `domainname 2>/dev/null`) unless (defined $dom || $^O =~ /^(?:cygwin|MSWin32|android)/); if (defined $dom) { my @h = (); $dom =~ s/^\.+//; while (length($dom)) { push(@h, "$host.$dom"); $dom =~ s/^[^.]+.+// or last; } unshift(@hosts, @h); } } # Attempt to locate FQDN foreach (grep { defined $_ } @hosts) { my @info = gethostbyname($_); next unless @info; # look at real name & aliases foreach my $site ($info[0], split(/ /, $info[1])) { if (rindex($site, ".") > 0) { # Extract domain from FQDN ($domain = $site) =~ s/\A[^.]+\.//; return $domain; } } } # Look for environment variable $domain ||= $ENV{LOCALDOMAIN} || $ENV{DOMAIN}; if (defined $domain) { $domain =~ s/[\r\n\0]+//g; $domain =~ s/(\A\.+|\.+\Z)//g; $domain =~ s/\.\.+/\./g; } $domain; } sub domainname { return $fqdn if (defined $fqdn); _hostname(); # *.local names are special on darwin. If we call gethostbyname below, it # may hang while waiting for another, non-existent computer to respond. if($^O eq 'darwin' && $host =~ /\.local$/) { return $host; } _hostdomain(); # Assumption: If the host name does not contain a period # and the domain name does, then assume that they are correct # this helps to eliminate calls to gethostbyname, and therefore # eliminate DNS lookups return $fqdn = $host . "." . $domain if (defined $host and defined $domain and $host !~ /\./ and $domain =~ /\./); # For hosts that have no name, just an IP address return $fqdn = $host if defined $host and $host =~ /^\d+(\.\d+){3}$/; my @host = defined $host ? split(/\./, $host) : ('localhost'); my @domain = defined $domain ? split(/\./, $domain) : (); my @fqdn = (); # Determine from @host & @domain the FQDN my @d = @domain; LOOP: while (1) { my @h = @host; while (@h) { my $tmp = join(".", @h, @d); if ((gethostbyname($tmp))[0]) { @fqdn = (@h, @d); $fqdn = $tmp; last LOOP; } pop @h; } last unless shift @d; } if (@fqdn) { $host = shift @fqdn; until ((gethostbyname($host))[0]) { $host .= "." . shift @fqdn; } $domain = join(".", @fqdn); } else { undef $host; undef $domain; undef $fqdn; } $fqdn; } sub hostfqdn { domainname() } sub hostname { domainname() unless (defined $host); return $host; } sub hostdomain { domainname() unless (defined $domain); return $domain; } 1; # Keep require happy __END__ package Net::protoent; use strict; use 5.006_001; our $VERSION = '1.01'; our(@EXPORT, @EXPORT_OK, %EXPORT_TAGS); our ( $p_name, @p_aliases, $p_proto ); BEGIN { use Exporter (); @EXPORT = qw(getprotobyname getprotobynumber getprotoent getproto); @EXPORT_OK = qw( $p_name @p_aliases $p_proto ); %EXPORT_TAGS = ( FIELDS => [ @EXPORT_OK, @EXPORT ] ); } # Class::Struct forbids use of @ISA sub import { goto &Exporter::import } use Class::Struct qw(struct); struct 'Net::protoent' => [ name => '$', aliases => '@', proto => '$', ]; sub populate (@) { return unless @_; my $pob = new(); $p_name = $pob->[0] = $_[0]; @p_aliases = @{ $pob->[1] } = split ' ', $_[1]; $p_proto = $pob->[2] = $_[2]; return $pob; } sub getprotoent ( ) { populate(CORE::getprotoent()) } sub getprotobyname ($) { populate(CORE::getprotobyname(shift)) } sub getprotobynumber ($) { populate(CORE::getprotobynumber(shift)) } sub getproto ($;$) { no strict 'refs'; return &{'getprotoby' . ($_[0]=~/^\d+$/ ? 'number' : 'name')}(@_); } 1; __END__ # Net::Time.pm # # Copyright (C) 1995-2004 Graham Barr. All rights reserved. # Copyright (C) 2014 Steve Hay. All rights reserved. # This module is free software; you can redistribute it and/or modify it under # the same terms as Perl itself, i.e. under the terms of either the GNU General # Public License or the Artistic License, as specified in the F file. package Net::Time; use 5.008001; use strict; use warnings; use Carp; use Exporter; use IO::Select; use IO::Socket; use Net::Config; our @ISA = qw(Exporter); our @EXPORT_OK = qw(inet_time inet_daytime); our $VERSION = "3.11"; our $TIMEOUT = 120; sub _socket { my ($pname, $pnum, $host, $proto, $timeout) = @_; $proto ||= 'udp'; my $port = (getservbyname($pname, $proto))[2] || $pnum; my $hosts = defined $host ? [$host] : $NetConfig{$pname . '_hosts'}; my $me; foreach my $addr (@$hosts) { $me = IO::Socket::INET->new( PeerAddr => $addr, PeerPort => $port, Proto => $proto ) and last; } return unless $me; $me->send("\n") if $proto eq 'udp'; $timeout = $TIMEOUT unless defined $timeout; IO::Select->new($me)->can_read($timeout) ? $me : undef; } sub inet_time { my $s = _socket('time', 37, @_) || return; my $buf = ''; my $offset = 0 | 0; return unless defined $s->recv($buf, length(pack("N", 0))); # unpack, we | 0 to ensure we have an unsigned my $time = (unpack("N", $buf))[0] | 0; # the time protocol return time in seconds since 1900, convert # it to a the required format if ($^O eq "MacOS") { # MacOS return seconds since 1904, 1900 was not a leap year. $offset = (4 * 31536000) | 0; } else { # otherwise return seconds since 1972, there were 17 leap years between # 1900 and 1972 $offset = (70 * 31536000 + 17 * 86400) | 0; } $time - $offset; } sub inet_daytime { my $s = _socket('daytime', 13, @_) || return; my $buf = ''; defined($s->recv($buf, 1024)) ? $buf : undef; } 1; __END__ package Net::servent; use strict; use 5.006_001; our $VERSION = '1.02'; our(@EXPORT, @EXPORT_OK, %EXPORT_TAGS); our ( $s_name, @s_aliases, $s_port, $s_proto ); BEGIN { use Exporter (); @EXPORT = qw(getservbyname getservbyport getservent getserv); @EXPORT_OK = qw( $s_name @s_aliases $s_port $s_proto ); %EXPORT_TAGS = ( FIELDS => [ @EXPORT_OK, @EXPORT ] ); } # Class::Struct forbids use of @ISA sub import { goto &Exporter::import } use Class::Struct qw(struct); struct 'Net::servent' => [ name => '$', aliases => '@', port => '$', proto => '$', ]; sub populate (@) { return unless @_; my $sob = new(); $s_name = $sob->[0] = $_[0]; @s_aliases = @{ $sob->[1] } = split ' ', $_[1]; $s_port = $sob->[2] = $_[2]; $s_proto = $sob->[3] = $_[3]; return $sob; } sub getservent ( ) { populate(CORE::getservent()) } sub getservbyname ($;$) { populate(CORE::getservbyname(shift,shift||'tcp')) } sub getservbyport ($;$) { populate(CORE::getservbyport(shift,shift||'tcp')) } sub getserv ($;$) { no strict 'refs'; return &{'getservby' . ($_[0]=~/^\d+$/ ? 'port' : 'name')}(@_); } 1; __END__ package Net::Ping; require 5.002; require Exporter; use strict; our $hires; use Fcntl qw( F_GETFL F_SETFL O_NONBLOCK ); use Socket qw( SOCK_DGRAM SOCK_STREAM SOCK_RAW AF_INET PF_INET IPPROTO_TCP SOL_SOCKET SO_ERROR SO_BROADCAST IPPROTO_IP IP_TOS IP_TTL inet_ntoa inet_aton getnameinfo sockaddr_in ); use POSIX qw( ENOTCONN ECONNREFUSED ECONNRESET EINPROGRESS EWOULDBLOCK EAGAIN WNOHANG ); use FileHandle; use Carp; use Time::HiRes; our @ISA = qw(Exporter); our @EXPORT = qw(pingecho); our @EXPORT_OK = qw(wakeonlan); our $VERSION = "2.62"; # Globals our $def_timeout = 5; # Default timeout to wait for a reply our $def_proto = "tcp"; # Default protocol to use for pinging our $def_factor = 1.2; # Default exponential backoff rate. our $def_family = AF_INET; # Default family. our $max_datasize = 1024; # Maximum data bytes in a packet # The data we exchange with the server for the stream protocol our $pingstring = "pingschwingping!\n"; our $source_verify = 1; # Default is to verify source endpoint our $syn_forking = 0; # Constants my $AF_INET6 = eval { Socket::AF_INET6() } || 30; my $AF_UNSPEC = eval { Socket::AF_UNSPEC() }; my $AI_NUMERICHOST = eval { Socket::AI_NUMERICHOST() } || 4; my $NI_NUMERICHOST = eval { Socket::NI_NUMERICHOST() } || 2; my $IPPROTO_IPV6 = eval { Socket::IPPROTO_IPV6() } || 41; #my $IPV6_HOPLIMIT = eval { Socket::IPV6_HOPLIMIT() }; # ping6 -h 0-255 my $qr_family = qr/^(?:(?:(:?ip)?v?(?:4|6))|${\AF_INET}|$AF_INET6)$/; my $qr_family4 = qr/^(?:(?:(:?ip)?v?4)|${\AF_INET})$/; if ($^O =~ /Win32/i) { # Hack to avoid this Win32 spewage: # Your vendor has not defined POSIX macro ECONNREFUSED my @pairs = (ECONNREFUSED => 10061, # "Unknown Error" Special Win32 Response? ENOTCONN => 10057, ECONNRESET => 10054, EINPROGRESS => 10036, EWOULDBLOCK => 10035, ); while (my $name = shift @pairs) { my $value = shift @pairs; # When defined, these all are non-zero unless (eval $name) { no strict 'refs'; *{$name} = defined prototype \&{$name} ? sub () {$value} : sub {$value}; } } # $syn_forking = 1; # XXX possibly useful in < Win2K ? }; # Description: The pingecho() subroutine is provided for backward # compatibility with the original Net::Ping. It accepts a host # name/IP and an optional timeout in seconds. Create a tcp ping # object and try pinging the host. The result of the ping is returned. sub pingecho { my ($host, # Name or IP number of host to ping $timeout # Optional timeout in seconds ) = @_; my ($p); # A ping object $p = Net::Ping->new("tcp", $timeout); $p->ping($host); # Going out of scope closes the connection } # Description: The new() method creates a new ping object. Optional # parameters may be specified for the protocol to use, the timeout in # seconds and the size in bytes of additional data which should be # included in the packet. # After the optional parameters are checked, the data is constructed # and a socket is opened if appropriate. The object is returned. sub new { my ($this, $proto, # Optional protocol to use for pinging $timeout, # Optional timeout in seconds $data_size, # Optional additional bytes of data $device, # Optional device to use $tos, # Optional ToS to set $ttl, # Optional TTL to set $family, # Optional address family (AF_INET) ) = @_; my $class = ref($this) || $this; my $self = {}; my ($cnt, # Count through data bytes $min_datasize # Minimum data bytes required ); bless($self, $class); if (ref $proto eq 'HASH') { # support named args for my $k (qw(proto timeout data_size device tos ttl family gateway host port bind retrans pingstring source_verify econnrefused dontfrag IPV6_USE_MIN_MTU IPV6_RECVPATHMTU IPV6_HOPLIMIT)) { if (exists $proto->{$k}) { $self->{$k} = $proto->{$k}; # some are still globals if ($k eq 'pingstring') { $pingstring = $proto->{$k} } if ($k eq 'source_verify') { $source_verify = $proto->{$k} } delete $proto->{$k}; } } if (%$proto) { croak("Invalid named argument: ",join(" ",keys (%$proto))); } $proto = $self->{'proto'}; } $proto = $def_proto unless $proto; # Determine the protocol croak('Protocol for ping must be "icmp", "icmpv6", "udp", "tcp", "syn", "stream" or "external"') unless $proto =~ m/^(icmp|icmpv6|udp|tcp|syn|stream|external)$/; $self->{proto} = $proto; $timeout = $def_timeout unless $timeout; # Determine the timeout croak("Default timeout for ping must be greater than 0 seconds") if $timeout <= 0; $self->{timeout} = $timeout; $self->{device} = $device; $self->{tos} = $tos; if ($self->{'host'}) { my $host = $self->{'host'}; my $ip = _resolv($host) or croak("could not resolve host $host"); $self->{host} = $ip; $self->{family} = $ip->{family}; } if ($self->{bind}) { my $addr = $self->{bind}; my $ip = _resolv($addr) or croak("could not resolve local addr $addr"); $self->{local_addr} = $ip; } else { $self->{local_addr} = undef; # Don't bind by default } if ($self->{proto} eq 'icmp') { croak('TTL must be from 0 to 255') if ($ttl && ($ttl < 0 || $ttl > 255)); $self->{ttl} = $ttl; } if ($family) { if ($family =~ $qr_family) { if ($family =~ $qr_family4) { $self->{family} = AF_INET; } else { $self->{family} = $AF_INET6; } } else { croak('Family must be "ipv4" or "ipv6"') } } else { $self->{family} = $def_family; } $min_datasize = ($proto eq "udp") ? 1 : 0; # Determine data size $data_size = $min_datasize unless defined($data_size) && $proto ne "tcp"; croak("Data for ping must be from $min_datasize to $max_datasize bytes") if ($data_size < $min_datasize) || ($data_size > $max_datasize); $data_size-- if $self->{proto} eq "udp"; # We provide the first byte $self->{data_size} = $data_size; $self->{data} = ""; # Construct data bytes for ($cnt = 0; $cnt < $self->{data_size}; $cnt++) { $self->{data} .= chr($cnt % 256); } # Default exponential backoff rate $self->{retrans} = $def_factor unless exists $self->{retrans}; # Default Connection refused behavior $self->{econnrefused} = undef unless exists $self->{econnrefused}; $self->{seq} = 0; # For counting packets if ($self->{proto} eq "udp") # Open a socket { $self->{proto_num} = eval { (getprotobyname('udp'))[2] } || croak("Can't udp protocol by name"); $self->{port_num} = $self->{port} || (getservbyname('echo', 'udp'))[2] || croak("Can't get udp echo port by name"); $self->{fh} = FileHandle->new(); socket($self->{fh}, PF_INET, SOCK_DGRAM, $self->{proto_num}) || croak("udp socket error - $!"); $self->_setopts(); } elsif ($self->{proto} eq "icmp") { croak("icmp ping requires root privilege") if !_isroot(); $self->{proto_num} = eval { (getprotobyname('icmp'))[2] } || croak("Can't get icmp protocol by name"); $self->{pid} = $$ & 0xffff; # Save lower 16 bits of pid $self->{fh} = FileHandle->new(); socket($self->{fh}, PF_INET, SOCK_RAW, $self->{proto_num}) || croak("icmp socket error - $!"); $self->_setopts(); if ($self->{'ttl'}) { setsockopt($self->{fh}, IPPROTO_IP, IP_TTL, pack("I*", $self->{'ttl'})) or croak "error configuring ttl to $self->{'ttl'} $!"; } } elsif ($self->{proto} eq "icmpv6") { croak("icmpv6 ping requires root privilege") if !_isroot(); croak("Wrong family $self->{family} for icmpv6 protocol") if $self->{family} and $self->{family} != $AF_INET6; $self->{family} = $AF_INET6; $self->{proto_num} = eval { (getprotobyname('ipv6-icmp'))[2] } || croak("Can't get ipv6-icmp protocol by name"); # 58 $self->{pid} = $$ & 0xffff; # Save lower 16 bits of pid $self->{fh} = FileHandle->new(); socket($self->{fh}, $AF_INET6, SOCK_RAW, $self->{proto_num}) || croak("icmp socket error - $!"); $self->_setopts(); if ($self->{'gateway'}) { my $g = $self->{gateway}; my $ip = _resolv($g) or croak("nonexistent gateway $g"); $self->{family} eq $AF_INET6 or croak("gateway requires the AF_INET6 family"); $ip->{family} eq $AF_INET6 or croak("gateway address needs to be IPv6"); my $IPV6_NEXTHOP = eval { Socket::IPV6_NEXTHOP() } || 48; # IPV6_3542NEXTHOP, or 21 setsockopt($self->{fh}, $IPPROTO_IPV6, $IPV6_NEXTHOP, _pack_sockaddr_in($ip)) or croak "error configuring gateway to $g NEXTHOP $!"; } if (exists $self->{IPV6_USE_MIN_MTU}) { my $IPV6_USE_MIN_MTU = eval { Socket::IPV6_USE_MIN_MTU() } || 42; setsockopt($self->{fh}, $IPPROTO_IPV6, $IPV6_USE_MIN_MTU, pack("I*", $self->{'IPV6_USE_MIN_MT'})) or croak "error configuring IPV6_USE_MIN_MT} $!"; } if (exists $self->{IPV6_RECVPATHMTU}) { my $IPV6_RECVPATHMTU = eval { Socket::IPV6_RECVPATHMTU() } || 43; setsockopt($self->{fh}, $IPPROTO_IPV6, $IPV6_RECVPATHMTU, pack("I*", $self->{'RECVPATHMTU'})) or croak "error configuring IPV6_RECVPATHMTU $!"; } if ($self->{'tos'}) { my $proto = $self->{family} == AF_INET ? IPPROTO_IP : $IPPROTO_IPV6; setsockopt($self->{fh}, $proto, IP_TOS, pack("I*", $self->{'tos'})) or croak "error configuring tos to $self->{'tos'} $!"; } if ($self->{'ttl'}) { my $proto = $self->{family} == AF_INET ? IPPROTO_IP : $IPPROTO_IPV6; setsockopt($self->{fh}, $proto, IP_TTL, pack("I*", $self->{'ttl'})) or croak "error configuring ttl to $self->{'ttl'} $!"; } } elsif ($self->{proto} eq "tcp" || $self->{proto} eq "stream") { $self->{proto_num} = eval { (getprotobyname('tcp'))[2] } || croak("Can't get tcp protocol by name"); $self->{port_num} = $self->{port} || (getservbyname('echo', 'tcp'))[2] || croak("Can't get tcp echo port by name"); $self->{fh} = FileHandle->new(); } elsif ($self->{proto} eq "syn") { $self->{proto_num} = eval { (getprotobyname('tcp'))[2] } || croak("Can't get tcp protocol by name"); $self->{port_num} = (getservbyname('echo', 'tcp'))[2] || croak("Can't get tcp echo port by name"); if ($syn_forking) { $self->{fork_rd} = FileHandle->new(); $self->{fork_wr} = FileHandle->new(); pipe($self->{fork_rd}, $self->{fork_wr}); $self->{fh} = FileHandle->new(); $self->{good} = {}; $self->{bad} = {}; } else { $self->{wbits} = ""; $self->{bad} = {}; } $self->{syn} = {}; $self->{stop_time} = 0; } return($self); } # Description: Set the local IP address from which pings will be sent. # For ICMP, UDP and TCP pings, just saves the address to be used when # the socket is opened. Returns non-zero if successful; croaks on error. sub bind { my ($self, $local_addr # Name or IP number of local interface ) = @_; my ($ip, # Hash of addr (string), addr_in (packed), family $h # resolved hash ); croak("Usage: \$p->bind(\$local_addr)") unless @_ == 2; croak("already bound") if defined($self->{local_addr}) && ($self->{proto} eq "udp" || $self->{proto} eq "icmp"); $ip = $self->_resolv($local_addr); croak("nonexistent local address $local_addr") unless defined($ip); $self->{local_addr} = $ip; if (($self->{proto} ne "udp") && ($self->{proto} ne "icmp") && ($self->{proto} ne "tcp") && ($self->{proto} ne "syn")) { croak("Unknown protocol \"$self->{proto}\" in bind()"); } return 1; } # Description: A select() wrapper that compensates for platform # peculiarities. sub mselect { if ($_[3] > 0 and $^O eq 'MSWin32') { # On windows, select() doesn't process the message loop, # but sleep() will, allowing alarm() to interrupt the latter. # So we chop up the timeout into smaller pieces and interleave # select() and sleep() calls. my $t = $_[3]; my $gran = 0.5; # polling granularity in seconds my @args = @_; while (1) { $gran = $t if $gran > $t; my $nfound = select($_[0], $_[1], $_[2], $gran); undef $nfound if $nfound == -1; $t -= $gran; return $nfound if $nfound or !defined($nfound) or $t <= 0; sleep(0); ($_[0], $_[1], $_[2]) = @args; } } else { my $nfound = select($_[0], $_[1], $_[2], $_[3]); undef $nfound if $nfound == -1; return $nfound; } } # Description: Allow UDP source endpoint comparison to be # skipped for those remote interfaces that do # not response from the same endpoint. sub source_verify { my $self = shift; $source_verify = 1 unless defined ($source_verify = ((defined $self) && (ref $self)) ? shift() : $self); } # Description: Set whether or not the connect # behavior should enforce remote service # availability as well as reachability. sub service_check { my $self = shift; $self->{econnrefused} = 1 unless defined ($self->{econnrefused} = shift()); } sub tcp_service_check { service_check(@_); } # Description: Set exponential backoff for retransmission. # Should be > 1 to retain exponential properties. # If set to 0, retransmissions are disabled. sub retrans { my $self = shift; $self->{retrans} = shift; } sub _IsAdminUser { return unless $^O eq 'MSWin32' or $^O eq "cygwin"; return unless eval { require Win32 }; return unless defined &Win32::IsAdminUser; return Win32::IsAdminUser(); } sub _isroot { if (($> and $^O ne 'VMS' and $^O ne 'cygwin') or (($^O eq 'MSWin32' or $^O eq 'cygwin') and !_IsAdminUser()) or ($^O eq 'VMS' and (`write sys\$output f\$privilege("SYSPRV")` =~ m/FALSE/))) { return 0; } else { return 1; } } # Description: Sets ipv6 reachability # REACHCONF was removed in RFC3542, ping6 -R supports it. requires root. sub IPV6_REACHCONF { my $self = shift; my $on = shift; if ($on) { my $reachconf = eval { Socket::IPV6_REACHCONF() }; if (!$reachconf) { carp "IPV6_REACHCONF not supported on this platform"; return 0; } if (!_isroot()) { carp "IPV6_REACHCONF requires root permissions"; return 0; } $self->{IPV6_REACHCONF} = 1; } else { return $self->{IPV6_REACHCONF}; } } # Description: set it on or off. sub IPV6_USE_MIN_MTU { my $self = shift; my $on = shift; if (defined $on) { my $IPV6_USE_MIN_MTU = eval { Socket::IPV6_USE_MIN_MTU() } || 43; #if (!$IPV6_USE_MIN_MTU) { # carp "IPV6_USE_MIN_MTU not supported on this platform"; # return 0; #} $self->{IPV6_USE_MIN_MTU} = $on ? 1 : 0; setsockopt($self->{fh}, $IPPROTO_IPV6, $IPV6_USE_MIN_MTU, pack("I*", $self->{'IPV6_USE_MIN_MT'})) or croak "error configuring IPV6_USE_MIN_MT} $!"; } else { return $self->{IPV6_USE_MIN_MTU}; } } # Description: notify an according MTU sub IPV6_RECVPATHMTU { my $self = shift; my $on = shift; if ($on) { my $IPV6_RECVPATHMTU = eval { Socket::IPV6_RECVPATHMTU() } || 43; #if (!$RECVPATHMTU) { # carp "IPV6_RECVPATHMTU not supported on this platform"; # return 0; #} $self->{IPV6_RECVPATHMTU} = 1; setsockopt($self->{fh}, $IPPROTO_IPV6, $IPV6_RECVPATHMTU, pack("I*", $self->{'IPV6_RECVPATHMTU'})) or croak "error configuring IPV6_RECVPATHMTU} $!"; } else { return $self->{IPV6_RECVPATHMTU}; } } # Description: allows the module to use milliseconds as returned by # the Time::HiRes module $hires = 1; sub hires { my $self = shift; $hires = 1 unless defined ($hires = ((defined $self) && (ref $self)) ? shift() : $self); } sub time { return $hires ? Time::HiRes::time() : CORE::time(); } # Description: Sets or clears the O_NONBLOCK flag on a file handle. sub socket_blocking_mode { my ($self, $fh, # the file handle whose flags are to be modified $block) = @_; # if true then set the blocking # mode (clear O_NONBLOCK), otherwise # set the non-blocking mode (set O_NONBLOCK) my $flags; if ($^O eq 'MSWin32' || $^O eq 'VMS') { # FIONBIO enables non-blocking sockets on windows and vms. # FIONBIO is (0x80000000|(4<<16)|(ord('f')<<8)|126), as per winsock.h, ioctl.h my $f = 0x8004667e; my $v = pack("L", $block ? 0 : 1); ioctl($fh, $f, $v) or croak("ioctl failed: $!"); return; } if ($flags = fcntl($fh, F_GETFL, 0)) { $flags = $block ? ($flags & ~O_NONBLOCK) : ($flags | O_NONBLOCK); if (!fcntl($fh, F_SETFL, $flags)) { croak("fcntl F_SETFL: $!"); } } else { croak("fcntl F_GETFL: $!"); } } # Description: Ping a host name or IP number with an optional timeout. # First lookup the host, and return undef if it is not found. Otherwise # perform the specific ping method based on the protocol. Return the # result of the ping. sub ping { my ($self, $host, # Name or IP number of host to ping $timeout, # Seconds after which ping times out $family, # Address family ) = @_; my ($ip, # Hash of addr (string), addr_in (packed), family $ret, # The return value $ping_time, # When ping began ); $host = $self->{host} if !defined $host and $self->{host}; croak("Usage: \$p->ping([ \$host [, \$timeout [, \$family]]])") if @_ > 4 or !$host; $timeout = $self->{timeout} unless $timeout; croak("Timeout must be greater than 0 seconds") if $timeout <= 0; if ($family) { if ($family =~ $qr_family) { if ($family =~ $qr_family4) { $self->{family_local} = AF_INET; } else { $self->{family_local} = $AF_INET6; } } else { croak('Family must be "ipv4" or "ipv6"') } } else { $self->{family_local} = $self->{family}; } $ip = $self->_resolv($host); return () unless defined($ip); # Does host exist? # Dispatch to the appropriate routine. $ping_time = &time(); if ($self->{proto} eq "external") { $ret = $self->ping_external($ip, $timeout); } elsif ($self->{proto} eq "udp") { $ret = $self->ping_udp($ip, $timeout); } elsif ($self->{proto} eq "icmp") { $ret = $self->ping_icmp($ip, $timeout); } elsif ($self->{proto} eq "icmpv6") { $ret = $self->ping_icmpv6($ip, $timeout); } elsif ($self->{proto} eq "tcp") { $ret = $self->ping_tcp($ip, $timeout); } elsif ($self->{proto} eq "stream") { $ret = $self->ping_stream($ip, $timeout); } elsif ($self->{proto} eq "syn") { $ret = $self->ping_syn($host, $ip, $ping_time, $ping_time+$timeout); } else { croak("Unknown protocol \"$self->{proto}\" in ping()"); } return wantarray ? ($ret, &time() - $ping_time, $self->ntop($ip)) : $ret; } # Uses Net::Ping::External to do an external ping. sub ping_external { my ($self, $ip, # Hash of addr (string), addr_in (packed), family $timeout, # Seconds after which ping times out $family ) = @_; $ip = $self->{host} if !defined $ip and $self->{host}; $timeout = $self->{timeout} if !defined $timeout and $self->{timeout}; my @addr = exists $ip->{addr_in} ? ('ip' => $ip->{addr_in}) : ('host' => $ip->{host}); eval { local @INC = @INC; pop @INC if $INC[-1] eq '.'; require Net::Ping::External; } or croak('Protocol "external" not supported on your system: Net::Ping::External not found'); return Net::Ping::External::ping(@addr, timeout => $timeout, family => $family); } # h2ph "asm/socket.h" # require "asm/socket.ph"; use constant SO_BINDTODEVICE => 25; use constant ICMP_ECHOREPLY => 0; # ICMP packet types use constant ICMPv6_ECHOREPLY => 129; # ICMP packet types use constant ICMP_UNREACHABLE => 3; # ICMP packet types use constant ICMPv6_UNREACHABLE => 1; # ICMP packet types use constant ICMP_ECHO => 8; use constant ICMPv6_ECHO => 128; use constant ICMP_TIME_EXCEEDED => 11; # ICMP packet types use constant ICMP_PARAMETER_PROBLEM => 12; # ICMP packet types use constant ICMP_STRUCT => "C2 n3 A"; # Structure of a minimal ICMP packet use constant SUBCODE => 0; # No ICMP subcode for ECHO and ECHOREPLY use constant ICMP_FLAGS => 0; # No special flags for send or recv use constant ICMP_PORT => 0; # No port with ICMP use constant IP_MTU_DISCOVER => 10; # linux only sub ping_icmp { my ($self, $ip, # Hash of addr (string), addr_in (packed), family $timeout # Seconds after which ping times out ) = @_; my ($saddr, # sockaddr_in with port and ip $checksum, # Checksum of ICMP packet $msg, # ICMP packet to send $len_msg, # Length of $msg $rbits, # Read bits, filehandles for reading $nfound, # Number of ready filehandles found $finish_time, # Time ping should be finished $done, # set to 1 when we are done $ret, # Return value $recv_msg, # Received message including IP header $from_saddr, # sockaddr_in of sender $from_port, # Port packet was sent from $from_ip, # Packed IP of sender $from_type, # ICMP type $from_subcode, # ICMP subcode $from_chk, # ICMP packet checksum $from_pid, # ICMP packet id $from_seq, # ICMP packet sequence $from_msg # ICMP message ); $ip = $self->{host} if !defined $ip and $self->{host}; $timeout = $self->{timeout} if !defined $timeout and $self->{timeout}; socket($self->{fh}, $ip->{family}, SOCK_RAW, $self->{proto_num}) || croak("icmp socket error - $!"); if (defined $self->{local_addr} && !CORE::bind($self->{fh}, _pack_sockaddr_in(0, $self->{local_addr}))) { croak("icmp bind error - $!"); } $self->_setopts(); $self->{seq} = ($self->{seq} + 1) % 65536; # Increment sequence $checksum = 0; # No checksum for starters if ($ip->{family} == AF_INET) { $msg = pack(ICMP_STRUCT . $self->{data_size}, ICMP_ECHO, SUBCODE, $checksum, $self->{pid}, $self->{seq}, $self->{data}); } else { # how to get SRC my $pseudo_header = pack('a16a16Nnn', $ip->{addr_in}, $ip->{addr_in}, 8+length($self->{data}), "\0", 0x003a); $msg = pack(ICMP_STRUCT . $self->{data_size}, ICMPv6_ECHO, SUBCODE, $checksum, $self->{pid}, $self->{seq}, $self->{data}); $msg = $pseudo_header.$msg } $checksum = Net::Ping->checksum($msg); if ($ip->{family} == AF_INET) { $msg = pack(ICMP_STRUCT . $self->{data_size}, ICMP_ECHO, SUBCODE, $checksum, $self->{pid}, $self->{seq}, $self->{data}); } else { $msg = pack(ICMP_STRUCT . $self->{data_size}, ICMPv6_ECHO, SUBCODE, $checksum, $self->{pid}, $self->{seq}, $self->{data}); } $len_msg = length($msg); $saddr = _pack_sockaddr_in(ICMP_PORT, $ip); $self->{from_ip} = undef; $self->{from_type} = undef; $self->{from_subcode} = undef; send($self->{fh}, $msg, ICMP_FLAGS, $saddr); # Send the message $rbits = ""; vec($rbits, $self->{fh}->fileno(), 1) = 1; $ret = 0; $done = 0; $finish_time = &time() + $timeout; # Must be done by this time while (!$done && $timeout > 0) # Keep trying if we have time { $nfound = mselect((my $rout=$rbits), undef, undef, $timeout); # Wait for packet $timeout = $finish_time - &time(); # Get remaining time if (!defined($nfound)) # Hmm, a strange error { $ret = undef; $done = 1; } elsif ($nfound) # Got a packet from somewhere { $recv_msg = ""; $from_pid = -1; $from_seq = -1; $from_saddr = recv($self->{fh}, $recv_msg, 1500, ICMP_FLAGS); ($from_port, $from_ip) = _unpack_sockaddr_in($from_saddr, $ip->{family}); ($from_type, $from_subcode) = unpack("C2", substr($recv_msg, 20, 2)); if ($from_type == ICMP_ECHOREPLY) { ($from_pid, $from_seq) = unpack("n3", substr($recv_msg, 24, 4)) if length $recv_msg >= 28; } elsif ($from_type == ICMPv6_ECHOREPLY) { ($from_pid, $from_seq) = unpack("n3", substr($recv_msg, 24, 4)) if length $recv_msg >= 28; } else { ($from_pid, $from_seq) = unpack("n3", substr($recv_msg, 52, 4)) if length $recv_msg >= 56; } $self->{from_ip} = $from_ip; $self->{from_type} = $from_type; $self->{from_subcode} = $from_subcode; next if ($from_pid != $self->{pid}); next if ($from_seq != $self->{seq}); if (! $source_verify || ($self->ntop($from_ip) eq $self->ntop($ip))) { # Does the packet check out? if (($from_type == ICMP_ECHOREPLY) || ($from_type == ICMPv6_ECHOREPLY)) { $ret = 1; $done = 1; } elsif (($from_type == ICMP_UNREACHABLE) || ($from_type == ICMPv6_UNREACHABLE)) { $done = 1; } elsif ($from_type == ICMP_TIME_EXCEEDED) { $ret = 0; $done = 1; } } } else { # Oops, timed out $done = 1; } } return $ret; } sub ping_icmpv6 { shift->ping_icmp(@_); } sub icmp_result { my ($self) = @_; my $addr = $self->{from_ip} || ""; $addr = "\0\0\0\0" unless 4 == length $addr; return ($self->ntop($addr),($self->{from_type} || 0), ($self->{from_subcode} || 0)); } # Description: Do a checksum on the message. Basically sum all of # the short words and fold the high order bits into the low order bits. sub checksum { my ($class, $msg # The message to checksum ) = @_; my ($len_msg, # Length of the message $num_short, # The number of short words in the message $short, # One short word $chk # The checksum ); $len_msg = length($msg); $num_short = int($len_msg / 2); $chk = 0; foreach $short (unpack("n$num_short", $msg)) { $chk += $short; } # Add the odd byte in $chk += (unpack("C", substr($msg, $len_msg - 1, 1)) << 8) if $len_msg % 2; $chk = ($chk >> 16) + ($chk & 0xffff); # Fold high into low return(~(($chk >> 16) + $chk) & 0xffff); # Again and complement } # Description: Perform a tcp echo ping. Since a tcp connection is # host specific, we have to open and close each connection here. We # can't just leave a socket open. Because of the robust nature of # tcp, it will take a while before it gives up trying to establish a # connection. Therefore, we use select() on a non-blocking socket to # check against our timeout. No data bytes are actually # sent since the successful establishment of a connection is proof # enough of the reachability of the remote host. Also, tcp is # expensive and doesn't need our help to add to the overhead. sub ping_tcp { my ($self, $ip, # Hash of addr (string), addr_in (packed), family $timeout # Seconds after which ping times out ) = @_; my ($ret # The return value ); $ip = $self->{host} if !defined $ip and $self->{host}; $timeout = $self->{timeout} if !defined $timeout and $self->{timeout}; $! = 0; $ret = $self -> tcp_connect( $ip, $timeout); if (!$self->{econnrefused} && $! == ECONNREFUSED) { $ret = 1; # "Connection refused" means reachable } $self->{fh}->close(); return $ret; } sub tcp_connect { my ($self, $ip, # Hash of addr (string), addr_in (packed), family $timeout # Seconds after which connect times out ) = @_; my ($saddr); # Packed IP and Port $ip = $self->{host} if !defined $ip and $self->{host}; $timeout = $self->{timeout} if !defined $timeout and $self->{timeout}; $saddr = _pack_sockaddr_in($self->{port_num}, $ip); my $ret = 0; # Default to unreachable my $do_socket = sub { socket($self->{fh}, $ip->{family}, SOCK_STREAM, $self->{proto_num}) || croak("tcp socket error - $!"); if (defined $self->{local_addr} && !CORE::bind($self->{fh}, _pack_sockaddr_in(0, $self->{local_addr}))) { croak("tcp bind error - $!"); } $self->_setopts(); }; my $do_connect = sub { $self->{ip} = $ip->{addr_in}; # ECONNREFUSED is 10061 on MSWin32. If we pass it as child error through $?, # we'll get (10061 & 255) = 77, so we cannot check it in the parent process. return ($ret = connect($self->{fh}, $saddr) || ($! == ECONNREFUSED && !$self->{econnrefused})); }; my $do_connect_nb = sub { # Set O_NONBLOCK property on filehandle $self->socket_blocking_mode($self->{fh}, 0); # start the connection attempt if (!connect($self->{fh}, $saddr)) { if ($! == ECONNREFUSED) { $ret = 1 unless $self->{econnrefused}; } elsif ($! != EINPROGRESS && ($^O ne 'MSWin32' || $! != EWOULDBLOCK)) { # EINPROGRESS is the expected error code after a connect() # on a non-blocking socket. But if the kernel immediately # determined that this connect() will never work, # Simply respond with "unreachable" status. # (This can occur on some platforms with errno # EHOSTUNREACH or ENETUNREACH.) return 0; } else { # Got the expected EINPROGRESS. # Just wait for connection completion... my ($wbits, $wout, $wexc); $wout = $wexc = $wbits = ""; vec($wbits, $self->{fh}->fileno, 1) = 1; my $nfound = mselect(undef, ($wout = $wbits), ($^O eq 'MSWin32' ? ($wexc = $wbits) : undef), $timeout); warn("select: $!") unless defined $nfound; if ($nfound && vec($wout, $self->{fh}->fileno, 1)) { # the socket is ready for writing so the connection # attempt completed. test whether the connection # attempt was successful or not if (getpeername($self->{fh})) { # Connection established to remote host $ret = 1; } else { # TCP ACK will never come from this host # because there was an error connecting. # This should set $! to the correct error. my $char; sysread($self->{fh},$char,1); $! = ECONNREFUSED if ($! == EAGAIN && $^O =~ /cygwin/i); $ret = 1 if (!$self->{econnrefused} && $! == ECONNREFUSED); } } else { # the connection attempt timed out (or there were connect # errors on Windows) if ($^O =~ 'MSWin32') { # If the connect will fail on a non-blocking socket, # winsock reports ECONNREFUSED as an exception, and we # need to fetch the socket-level error code via getsockopt() # instead of using the thread-level error code that is in $!. if ($nfound && vec($wexc, $self->{fh}->fileno, 1)) { $! = unpack("i", getsockopt($self->{fh}, SOL_SOCKET, SO_ERROR)); } } } } } else { # Connection established to remote host $ret = 1; } # Unset O_NONBLOCK property on filehandle $self->socket_blocking_mode($self->{fh}, 1); $self->{ip} = $ip->{addr_in}; return $ret; }; if ($syn_forking) { # Buggy Winsock API doesn't allow nonblocking connect. # Hence, if our OS is Windows, we need to create a separate # process to do the blocking connect attempt. # XXX Above comments are not true at least for Win2K, where # nonblocking connect works. $| = 1; # Clear buffer prior to fork to prevent duplicate flushing. $self->{'tcp_chld'} = fork; if (!$self->{'tcp_chld'}) { if (!defined $self->{'tcp_chld'}) { # Fork did not work warn "Fork error: $!"; return 0; } &{ $do_socket }(); # Try a slow blocking connect() call # and report the status to the parent. if ( &{ $do_connect }() ) { $self->{fh}->close(); # No error exit 0; } else { # Pass the error status to the parent # Make sure that $! <= 255 exit($! <= 255 ? $! : 255); } } &{ $do_socket }(); my $patience = &time() + $timeout; my ($child, $child_errno); $? = 0; $child_errno = 0; # Wait up to the timeout # And clean off the zombie do { $child = waitpid($self->{'tcp_chld'}, &WNOHANG()); $child_errno = $? >> 8; select(undef, undef, undef, 0.1); } while &time() < $patience && $child != $self->{'tcp_chld'}; if ($child == $self->{'tcp_chld'}) { if ($self->{proto} eq "stream") { # We need the socket connected here, in parent # Should be safe to connect because the child finished # within the timeout &{ $do_connect }(); } # $ret cannot be set by the child process $ret = !$child_errno; } else { # Time must have run out. # Put that choking client out of its misery kill "KILL", $self->{'tcp_chld'}; # Clean off the zombie waitpid($self->{'tcp_chld'}, 0); $ret = 0; } delete $self->{'tcp_chld'}; $! = $child_errno; } else { # Otherwise don't waste the resources to fork &{ $do_socket }(); &{ $do_connect_nb }(); } return $ret; } sub DESTROY { my $self = shift; if ($self->{'proto'} eq 'tcp' && $self->{'tcp_chld'}) { # Put that choking client out of its misery kill "KILL", $self->{'tcp_chld'}; # Clean off the zombie waitpid($self->{'tcp_chld'}, 0); } } # This writes the given string to the socket and then reads it # back. It returns 1 on success, 0 on failure. sub tcp_echo { my ($self, $timeout, $pingstring) = @_; $timeout = $self->{timeout} if !defined $timeout and $self->{timeout}; $pingstring = $self->{pingstring} if !defined $pingstring and $self->{pingstring}; my $ret = undef; my $time = &time(); my $wrstr = $pingstring; my $rdstr = ""; eval <<'EOM'; do { my $rin = ""; vec($rin, $self->{fh}->fileno(), 1) = 1; my $rout = undef; if($wrstr) { $rout = ""; vec($rout, $self->{fh}->fileno(), 1) = 1; } if(mselect($rin, $rout, undef, ($time + $timeout) - &time())) { if($rout && vec($rout,$self->{fh}->fileno(),1)) { my $num = syswrite($self->{fh}, $wrstr, length $wrstr); if($num) { # If it was a partial write, update and try again. $wrstr = substr($wrstr,$num); } else { # There was an error. $ret = 0; } } if(vec($rin,$self->{fh}->fileno(),1)) { my $reply; if(sysread($self->{fh},$reply,length($pingstring)-length($rdstr))) { $rdstr .= $reply; $ret = 1 if $rdstr eq $pingstring; } else { # There was an error. $ret = 0; } } } } until &time() > ($time + $timeout) || defined($ret); EOM return $ret; } # Description: Perform a stream ping. If the tcp connection isn't # already open, it opens it. It then sends some data and waits for # a reply. It leaves the stream open on exit. sub ping_stream { my ($self, $ip, # Hash of addr (string), addr_in (packed), family $timeout # Seconds after which ping times out ) = @_; # Open the stream if it's not already open if(!defined $self->{fh}->fileno()) { $self->tcp_connect($ip, $timeout) or return 0; } croak "tried to switch servers while stream pinging" if $self->{ip} ne $ip->{addr_in}; return $self->tcp_echo($timeout, $pingstring); } # Description: opens the stream. You would do this if you want to # separate the overhead of opening the stream from the first ping. sub open { my ($self, $host, # Host or IP address $timeout, # Seconds after which open times out $family ) = @_; my $ip; # Hash of addr (string), addr_in (packed), family $host = $self->{host} unless defined $host; if ($family) { if ($family =~ $qr_family) { if ($family =~ $qr_family4) { $self->{family_local} = AF_INET; } else { $self->{family_local} = $AF_INET6; } } else { croak('Family must be "ipv4" or "ipv6"') } } else { $self->{family_local} = $self->{family}; } $ip = $self->_resolv($host); $timeout = $self->{timeout} unless $timeout; if($self->{proto} eq "stream") { if(defined($self->{fh}->fileno())) { croak("socket is already open"); } else { $self->tcp_connect($ip, $timeout); } } } sub _dontfrag { my $self = shift; # bsd solaris my $IP_DONTFRAG = eval { Socket::IP_DONTFRAG() }; if ($IP_DONTFRAG) { my $i = 1; setsockopt($self->{fh}, IPPROTO_IP, $IP_DONTFRAG, pack("I*", $i)) or croak "error configuring IP_DONTFRAG $!"; # Linux needs more: Path MTU Discovery as defined in RFC 1191 # For non SOCK_STREAM sockets it is the user's responsibility to packetize # the data in MTU sized chunks and to do the retransmits if necessary. # The kernel will reject packets that are bigger than the known path # MTU if this flag is set (with EMSGSIZE). if ($^O eq 'linux') { my $i = 2; # IP_PMTUDISC_DO setsockopt($self->{fh}, IPPROTO_IP, IP_MTU_DISCOVER, pack("I*", $i)) or croak "error configuring IP_MTU_DISCOVER $!"; } } } # SO_BINDTODEVICE + IP_TOS sub _setopts { my $self = shift; if ($self->{'device'}) { setsockopt($self->{fh}, SOL_SOCKET, SO_BINDTODEVICE, pack("Z*", $self->{'device'})) or croak "error binding to device $self->{'device'} $!"; } if ($self->{'tos'}) { # need to re-apply ToS (RT #6706) setsockopt($self->{fh}, IPPROTO_IP, IP_TOS, pack("I*", $self->{'tos'})) or croak "error applying tos to $self->{'tos'} $!"; } if ($self->{'dontfrag'}) { $self->_dontfrag; } } # Description: Perform a udp echo ping. Construct a message of # at least the one-byte sequence number and any additional data bytes. # Send the message out and wait for a message to come back. If we # get a message, make sure all of its parts match. If they do, we are # done. Otherwise go back and wait for the message until we run out # of time. Return the result of our efforts. use constant UDP_FLAGS => 0; # Nothing special on send or recv sub ping_udp { my ($self, $ip, # Hash of addr (string), addr_in (packed), family $timeout # Seconds after which ping times out ) = @_; my ($saddr, # sockaddr_in with port and ip $ret, # The return value $msg, # Message to be echoed $finish_time, # Time ping should be finished $flush, # Whether socket needs to be disconnected $connect, # Whether socket needs to be connected $done, # Set to 1 when we are done pinging $rbits, # Read bits, filehandles for reading $nfound, # Number of ready filehandles found $from_saddr, # sockaddr_in of sender $from_msg, # Characters echoed by $host $from_port, # Port message was echoed from $from_ip # Packed IP number of sender ); $saddr = _pack_sockaddr_in($self->{port_num}, $ip); $self->{seq} = ($self->{seq} + 1) % 256; # Increment sequence $msg = chr($self->{seq}) . $self->{data}; # Add data if any socket($self->{fh}, $ip->{family}, SOCK_DGRAM, $self->{proto_num}) || croak("udp socket error - $!"); if (defined $self->{local_addr} && !CORE::bind($self->{fh}, _pack_sockaddr_in(0, $self->{local_addr}))) { croak("udp bind error - $!"); } $self->_setopts(); if ($self->{connected}) { if ($self->{connected} ne $saddr) { # Still connected to wrong destination. # Need to flush out the old one. $flush = 1; } } else { # Not connected yet. # Need to connect() before send() $connect = 1; } # Have to connect() and send() instead of sendto() # in order to pick up on the ECONNREFUSED setting # from recv() or double send() errno as utilized in # the concept by rdw @ perlmonks. See: # http://perlmonks.thepen.com/42898.html if ($flush) { # Need to socket() again to flush the descriptor # This will disconnect from the old saddr. socket($self->{fh}, $ip->{family}, SOCK_DGRAM, $self->{proto_num}); $self->_setopts(); } # Connect the socket if it isn't already connected # to the right destination. if ($flush || $connect) { connect($self->{fh}, $saddr); # Tie destination to socket $self->{connected} = $saddr; } send($self->{fh}, $msg, UDP_FLAGS); # Send it $rbits = ""; vec($rbits, $self->{fh}->fileno(), 1) = 1; $ret = 0; # Default to unreachable $done = 0; my $retrans = 0.01; my $factor = $self->{retrans}; $finish_time = &time() + $timeout; # Ping needs to be done by then while (!$done && $timeout > 0) { if ($factor > 1) { $timeout = $retrans if $timeout > $retrans; $retrans*= $factor; # Exponential backoff } $nfound = mselect((my $rout=$rbits), undef, undef, $timeout); # Wait for response my $why = $!; $timeout = $finish_time - &time(); # Get remaining time if (!defined($nfound)) # Hmm, a strange error { $ret = undef; $done = 1; } elsif ($nfound) # A packet is waiting { $from_msg = ""; $from_saddr = recv($self->{fh}, $from_msg, 1500, UDP_FLAGS); if (!$from_saddr) { # For example an unreachable host will make recv() fail. if (!$self->{econnrefused} && ($! == ECONNREFUSED || $! == ECONNRESET)) { # "Connection refused" means reachable # Good, continue $ret = 1; } $done = 1; } else { ($from_port, $from_ip) = _unpack_sockaddr_in($from_saddr, $ip->{family}); my $addr_in = ref($ip) eq "HASH" ? $ip->{addr_in} : $ip; if (!$source_verify || (($from_ip eq $addr_in) && # Does the packet check out? ($from_port == $self->{port_num}) && ($from_msg eq $msg))) { $ret = 1; # It's a winner $done = 1; } } } elsif ($timeout <= 0) # Oops, timed out { $done = 1; } else { # Send another in case the last one dropped if (send($self->{fh}, $msg, UDP_FLAGS)) { # Another send worked? The previous udp packet # must have gotten lost or is still in transit. # Hopefully this new packet will arrive safely. } else { if (!$self->{econnrefused} && $! == ECONNREFUSED) { # "Connection refused" means reachable # Good, continue $ret = 1; } $done = 1; } } } return $ret; } # Description: Send a TCP SYN packet to host specified. sub ping_syn { my $self = shift; my $host = shift; my $ip = shift; my $start_time = shift; my $stop_time = shift; if ($syn_forking) { return $self->ping_syn_fork($host, $ip, $start_time, $stop_time); } my $fh = FileHandle->new(); my $saddr = _pack_sockaddr_in($self->{port_num}, $ip); # Create TCP socket if (!socket ($fh, $ip->{family}, SOCK_STREAM, $self->{proto_num})) { croak("tcp socket error - $!"); } if (defined $self->{local_addr} && !CORE::bind($fh, _pack_sockaddr_in(0, $self->{local_addr}))) { croak("tcp bind error - $!"); } $self->_setopts(); # Set O_NONBLOCK property on filehandle $self->socket_blocking_mode($fh, 0); # Attempt the non-blocking connect # by just sending the TCP SYN packet if (connect($fh, $saddr)) { # Non-blocking, yet still connected? # Must have connected very quickly, # or else it wasn't very non-blocking. #warn "WARNING: Nonblocking connect connected anyway? ($^O)"; } else { # Error occurred connecting. if ($! == EINPROGRESS || ($^O eq 'MSWin32' && $! == EWOULDBLOCK)) { # The connection is just still in progress. # This is the expected condition. } else { # Just save the error and continue on. # The ack() can check the status later. $self->{bad}->{$host} = $!; } } my $entry = [ $host, $ip, $fh, $start_time, $stop_time, $self->{port_num} ]; $self->{syn}->{$fh->fileno} = $entry; if ($self->{stop_time} < $stop_time) { $self->{stop_time} = $stop_time; } vec($self->{wbits}, $fh->fileno, 1) = 1; return 1; } sub ping_syn_fork { my ($self, $host, $ip, $start_time, $stop_time) = @_; # Buggy Winsock API doesn't allow nonblocking connect. # Hence, if our OS is Windows, we need to create a separate # process to do the blocking connect attempt. my $pid = fork(); if (defined $pid) { if ($pid) { # Parent process my $entry = [ $host, $ip, $pid, $start_time, $stop_time ]; $self->{syn}->{$pid} = $entry; if ($self->{stop_time} < $stop_time) { $self->{stop_time} = $stop_time; } } else { # Child process my $saddr = _pack_sockaddr_in($self->{port_num}, $ip); # Create TCP socket if (!socket ($self->{fh}, $ip->{family}, SOCK_STREAM, $self->{proto_num})) { croak("tcp socket error - $!"); } if (defined $self->{local_addr} && !CORE::bind($self->{fh}, _pack_sockaddr_in(0, $self->{local_addr}))) { croak("tcp bind error - $!"); } $self->_setopts(); $!=0; # Try to connect (could take a long time) connect($self->{fh}, $saddr); # Notify parent of connect error status my $err = $!+0; my $wrstr = "$$ $err"; # Force to 16 chars including \n $wrstr .= " "x(15 - length $wrstr). "\n"; syswrite($self->{fork_wr}, $wrstr, length $wrstr); exit; } } else { # fork() failed? die "fork: $!"; } return 1; } # Description: Wait for TCP ACK from host specified # from ping_syn above. If no host is specified, wait # for TCP ACK from any of the hosts in the SYN queue. sub ack { my $self = shift; if ($self->{proto} eq "syn") { if ($syn_forking) { my @answer = $self->ack_unfork(shift); return wantarray ? @answer : $answer[0]; } my $wbits = ""; my $stop_time = 0; if (my $host = shift or $self->{host}) { # Host passed as arg or as option to new $host = $self->{host} unless defined $host; if (exists $self->{bad}->{$host}) { if (!$self->{econnrefused} && $self->{bad}->{ $host } && (($! = ECONNREFUSED)>0) && $self->{bad}->{ $host } eq "$!") { # "Connection refused" means reachable # Good, continue } else { # ECONNREFUSED means no good return (); } } my $host_fd = undef; foreach my $fd (keys %{ $self->{syn} }) { my $entry = $self->{syn}->{$fd}; if ($entry->[0] eq $host) { $host_fd = $fd; $stop_time = $entry->[4] || croak("Corrupted SYN entry for [$host]"); last; } } croak("ack called on [$host] without calling ping first!") unless defined $host_fd; vec($wbits, $host_fd, 1) = 1; } else { # No $host passed so scan all hosts # Use the latest stop_time $stop_time = $self->{stop_time}; # Use all the bits $wbits = $self->{wbits}; } while ($wbits !~ /^\0*\z/) { my $timeout = $stop_time - &time(); # Force a minimum of 10 ms timeout. $timeout = 0.01 if $timeout <= 0.01; my $winner_fd = undef; my $wout = $wbits; my $fd = 0; # Do "bad" fds from $wbits first while ($wout !~ /^\0*\z/) { if (vec($wout, $fd, 1)) { # Wipe it from future scanning. vec($wout, $fd, 1) = 0; if (my $entry = $self->{syn}->{$fd}) { if ($self->{bad}->{ $entry->[0] }) { $winner_fd = $fd; last; } } } $fd++; } if (defined($winner_fd) or my $nfound = mselect(undef, ($wout=$wbits), undef, $timeout)) { if (defined $winner_fd) { $fd = $winner_fd; } else { # Done waiting for one of the ACKs $fd = 0; # Determine which one while ($wout !~ /^\0*\z/ && !vec($wout, $fd, 1)) { $fd++; } } if (my $entry = $self->{syn}->{$fd}) { # Wipe it from future scanning. delete $self->{syn}->{$fd}; vec($self->{wbits}, $fd, 1) = 0; vec($wbits, $fd, 1) = 0; if (!$self->{econnrefused} && $self->{bad}->{ $entry->[0] } && (($! = ECONNREFUSED)>0) && $self->{bad}->{ $entry->[0] } eq "$!") { # "Connection refused" means reachable # Good, continue } elsif (getpeername($entry->[2])) { # Connection established to remote host # Good, continue } else { # TCP ACK will never come from this host # because there was an error connecting. # This should set $! to the correct error. my $char; sysread($entry->[2],$char,1); # Store the excuse why the connection failed. $self->{bad}->{$entry->[0]} = $!; if (!$self->{econnrefused} && (($! == ECONNREFUSED) || ($! == EAGAIN && $^O =~ /cygwin/i))) { # "Connection refused" means reachable # Good, continue } else { # No good, try the next socket... next; } } # Everything passed okay, return the answer return wantarray ? ($entry->[0], &time() - $entry->[3], $self->ntop($entry->[1]), $entry->[5]) : $entry->[0]; } else { warn "Corrupted SYN entry: unknown fd [$fd] ready!"; vec($wbits, $fd, 1) = 0; vec($self->{wbits}, $fd, 1) = 0; } } elsif (defined $nfound) { # Timed out waiting for ACK foreach my $fd (keys %{ $self->{syn} }) { if (vec($wbits, $fd, 1)) { my $entry = $self->{syn}->{$fd}; $self->{bad}->{$entry->[0]} = "Timed out"; vec($wbits, $fd, 1) = 0; vec($self->{wbits}, $fd, 1) = 0; delete $self->{syn}->{$fd}; } } } else { # Weird error occurred with select() warn("select: $!"); $self->{syn} = {}; $wbits = ""; } } } return (); } sub ack_unfork { my ($self,$host) = @_; my $stop_time = $self->{stop_time}; if ($host) { # Host passed as arg if (my $entry = $self->{good}->{$host}) { delete $self->{good}->{$host}; return ($entry->[0], &time() - $entry->[3], $self->ntop($entry->[1])); } } my $rbits = ""; my $timeout; if (keys %{ $self->{syn} }) { # Scan all hosts that are left vec($rbits, fileno($self->{fork_rd}), 1) = 1; $timeout = $stop_time - &time(); # Force a minimum of 10 ms timeout. $timeout = 0.01 if $timeout < 0.01; } else { # No hosts left to wait for $timeout = 0; } if ($timeout > 0) { my $nfound; while ( keys %{ $self->{syn} } and $nfound = mselect((my $rout=$rbits), undef, undef, $timeout)) { # Done waiting for one of the ACKs if (!sysread($self->{fork_rd}, $_, 16)) { # Socket closed, which means all children are done. return (); } my ($pid, $how) = split; if ($pid) { # Flush the zombie waitpid($pid, 0); if (my $entry = $self->{syn}->{$pid}) { # Connection attempt to remote host is done delete $self->{syn}->{$pid}; if (!$how || # If there was no error connecting (!$self->{econnrefused} && $how == ECONNREFUSED)) { # "Connection refused" means reachable if ($host && $entry->[0] ne $host) { # A good connection, but not the host we need. # Move it from the "syn" hash to the "good" hash. $self->{good}->{$entry->[0]} = $entry; # And wait for the next winner next; } return ($entry->[0], &time() - $entry->[3], $self->ntop($entry->[1])); } } else { # Should never happen die "Unknown ping from pid [$pid]"; } } else { die "Empty response from status socket?"; } } if (defined $nfound) { # Timed out waiting for ACK status } else { # Weird error occurred with select() warn("select: $!"); } } if (my @synners = keys %{ $self->{syn} }) { # Kill all the synners kill 9, @synners; foreach my $pid (@synners) { # Wait for the deaths to finish # Then flush off the zombie waitpid($pid, 0); } } $self->{syn} = {}; return (); } # Description: Tell why the ack() failed sub nack { my $self = shift; my $host = shift || croak('Usage> nack($failed_ack_host)'); return $self->{bad}->{$host} || undef; } # Description: Close the connection. sub close { my ($self) = @_; if ($self->{proto} eq "syn") { delete $self->{syn}; } elsif ($self->{proto} eq "tcp") { # The connection will already be closed } elsif ($self->{proto} eq "external") { # Nothing to close } else { $self->{fh}->close(); } } sub port_number { my $self = shift; if(@_) { $self->{port_num} = shift @_; $self->service_check(1); } return $self->{port_num}; } sub ntop { my($self, $ip) = @_; # Vista doesn't define a inet_ntop. It has InetNtop instead. # Not following ANSI... priceless. getnameinfo() is defined # for Windows 2000 and later, so that may be the choice. # Any port will work, even undef, but this will work for now. # Socket warns when undef is passed in, but it still works. my $port = getservbyname('echo', 'udp'); my $sockaddr = _pack_sockaddr_in($port, $ip); my ($error, $address) = getnameinfo($sockaddr, $NI_NUMERICHOST); croak $error if $error; return $address; } sub wakeonlan { my ($mac_addr, $host, $port) = @_; # use the discard service if $port not passed in if (! defined $host) { $host = '255.255.255.255' } if (! defined $port || $port !~ /^\d+$/ ) { $port = 9 } require IO::Socket::INET; my $sock = IO::Socket::INET->new(Proto=>'udp') || return undef; my $ip_addr = inet_aton($host); my $sock_addr = sockaddr_in($port, $ip_addr); $mac_addr =~ s/://g; my $packet = pack('C6H*', 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, $mac_addr x 16); setsockopt($sock, SOL_SOCKET, SO_BROADCAST, 1); send($sock, $packet, 0, $sock_addr); $sock->close; return 1; } ######################################################## # DNS hostname resolution # return: # $h->{name} = host - as passed in # $h->{host} = host - as passed in without :port # $h->{port} = OPTIONAL - if :port, then value of port # $h->{addr} = resolved numeric address # $h->{addr_in} = aton/pton result # $h->{family} = AF_INET/6 ############################ sub _resolv { my ($self, $name, ) = @_; my %h; $h{name} = $name; my $family = $self->{family}; if (defined($self->{family_local})) { $family = $self->{family_local} } # START - host:port my $cnt = 0; # Count ":" $cnt++ while ($name =~ m/:/g); # 0 = hostname or IPv4 address if ($cnt == 0) { $h{host} = $name # 1 = IPv4 address with port } elsif ($cnt == 1) { ($h{host}, $h{port}) = split /:/, $name # >=2 = IPv6 address } elsif ($cnt >= 2) { #IPv6 with port - [2001::1]:port if ($name =~ /^\[.*\]:\d{1,5}$/) { ($h{host}, $h{port}) = split /:([^:]+)$/, $name # split after last : # IPv6 without port } else { $h{host} = $name } } # Clean up host $h{host} =~ s/\[//g; $h{host} =~ s/\]//g; # Clean up port if (defined($h{port}) && (($h{port} !~ /^\d{1,5}$/) || ($h{port} < 1) || ($h{port} > 65535))) { croak("Invalid port `$h{port}' in `$name'"); } # END - host:port # address check # new way if ($Socket::VERSION >= 1.94) { my %hints = ( family => $AF_UNSPEC, protocol => IPPROTO_TCP, flags => $AI_NUMERICHOST ); # numeric address, return my ($err, @getaddr) = Socket::getaddrinfo($h{host}, undef, \%hints); if (defined($getaddr[0])) { $h{addr} = $h{host}; $h{family} = $getaddr[0]->{family}; if ($h{family} == AF_INET) { (undef, $h{addr_in}, undef, undef) = Socket::unpack_sockaddr_in $getaddr[0]->{addr}; } else { (undef, $h{addr_in}, undef, undef) = Socket::unpack_sockaddr_in6 $getaddr[0]->{addr}; } return \%h } # old way } else { # numeric address, return my $ret = gethostbyname($h{host}); if (defined($ret) && (_inet_ntoa($ret) eq $h{host})) { $h{addr} = $h{host}; $h{addr_in} = $ret; $h{family} = AF_INET; return \%h } } # resolve # new way if ($Socket::VERSION >= 1.94) { my %hints = ( family => $family, protocol => IPPROTO_TCP ); my ($err, @getaddr) = Socket::getaddrinfo($h{host}, undef, \%hints); if (defined($getaddr[0])) { my ($err, $address) = Socket::getnameinfo($getaddr[0]->{addr}, $NI_NUMERICHOST); if (defined($address)) { $h{addr} = $address; $h{addr} =~ s/\%(.)*$//; # remove %ifID if IPv6 $h{family} = $getaddr[0]->{family}; if ($h{family} == AF_INET) { (undef, $h{addr_in}, undef, undef) = Socket::unpack_sockaddr_in $getaddr[0]->{addr}; } else { (undef, $h{addr_in}, undef, undef) = Socket::unpack_sockaddr_in6 $getaddr[0]->{addr}; } return \%h } else { croak("getnameinfo($getaddr[0]->{addr}) failed - $err"); } } else { croak(sprintf("getaddrinfo($h{host},,%s) failed - $err", $family == AF_INET ? "AF_INET" : "AF_INET6")); } # old way } else { if ($family == $AF_INET6) { croak("Socket >= 1.94 required for IPv6 - found Socket $Socket::VERSION"); } my @gethost = gethostbyname($h{host}); if (defined($gethost[4])) { $h{addr} = inet_ntoa($gethost[4]); $h{addr_in} = $gethost[4]; $h{family} = AF_INET; return \%h } else { croak("gethostbyname($h{host}) failed - $^E"); } } } sub _pack_sockaddr_in($$) { my ($port, $ip, ) = @_; my $addr = ref($ip) eq "HASH" ? $ip->{addr_in} : $ip; if (length($addr) <= 4 ) { return Socket::pack_sockaddr_in($port, $addr); } else { return Socket::pack_sockaddr_in6($port, $addr); } } sub _unpack_sockaddr_in($;$) { my ($addr, $family, ) = @_; my ($port, $host); if ($family == AF_INET || (!defined($family) and length($addr) <= 16 )) { ($port, $host) = Socket::unpack_sockaddr_in($addr); } else { ($port, $host) = Socket::unpack_sockaddr_in6($addr); } return $port, $host } sub _inet_ntoa { my ($addr ) = @_; my $ret; if ($Socket::VERSION >= 1.94) { my ($err, $address) = Socket::getnameinfo($addr, $NI_NUMERICHOST); if (defined($address)) { $ret = $address; } else { croak("getnameinfo($addr) failed - $err"); } } else { $ret = inet_ntoa($addr) } return $ret } 1; __END__ # Net::NNTP.pm # # Copyright (C) 1995-1997 Graham Barr. All rights reserved. # Copyright (C) 2013-2016 Steve Hay. All rights reserved. # This module is free software; you can redistribute it and/or modify it under # the same terms as Perl itself, i.e. under the terms of either the GNU General # Public License or the Artistic License, as specified in the F file. package Net::NNTP; use 5.008001; use strict; use warnings; use Carp; use IO::Socket; use Net::Cmd; use Net::Config; use Time::Local; our $VERSION = "3.11"; # Code for detecting if we can use SSL my $ssl_class = eval { require IO::Socket::SSL; # first version with default CA on most platforms no warnings 'numeric'; IO::Socket::SSL->VERSION(2.007); } && 'IO::Socket::SSL'; my $nossl_warn = !$ssl_class && 'To use SSL please install IO::Socket::SSL with version>=2.007'; # Code for detecting if we can use IPv6 my $family_key = 'Domain'; my $inet6_class = eval { require IO::Socket::IP; no warnings 'numeric'; IO::Socket::IP->VERSION(0.25) || die; $family_key = 'Family'; } && 'IO::Socket::IP' || eval { require IO::Socket::INET6; no warnings 'numeric'; IO::Socket::INET6->VERSION(2.62); } && 'IO::Socket::INET6'; sub can_ssl { $ssl_class }; sub can_inet6 { $inet6_class }; our @ISA = ('Net::Cmd', $inet6_class || 'IO::Socket::INET'); sub new { my $self = shift; my $type = ref($self) || $self; my ($host, %arg); if (@_ % 2) { $host = shift; %arg = @_; } else { %arg = @_; $host = delete $arg{Host}; } my $obj; $host ||= $ENV{NNTPSERVER} || $ENV{NEWSHOST}; my $hosts = defined $host ? [$host] : $NetConfig{nntp_hosts}; @{$hosts} = qw(news) unless @{$hosts}; my %connect = ( Proto => 'tcp'); if ($arg{SSL}) { # SSL from start die $nossl_warn if ! $ssl_class; $arg{Port} ||= 563; $connect{$_} = $arg{$_} for(grep { m{^SSL_} } keys %arg); } foreach my $o (qw(LocalAddr LocalPort Timeout)) { $connect{$o} = $arg{$o} if exists $arg{$o}; } $connect{$family_key} = $arg{Domain} || $arg{Family}; $connect{Timeout} = 120 unless defined $connect{Timeout}; $connect{PeerPort} = $arg{Port} || 'nntp(119)'; foreach my $h (@{$hosts}) { $connect{PeerAddr} = $h; $obj = $type->SUPER::new(%connect) or next; ${*$obj}{'net_nntp_host'} = $h; ${*$obj}{'net_nntp_arg'} = \%arg; if ($arg{SSL}) { Net::NNTP::_SSL->start_SSL($obj,%arg) or next; } last: } return unless defined $obj; $obj->autoflush(1); $obj->debug(exists $arg{Debug} ? $arg{Debug} : undef); unless ($obj->response() == CMD_OK) { $obj->close; return; } my $c = $obj->code; my @m = $obj->message; unless (exists $arg{Reader} && $arg{Reader} == 0) { # if server is INN and we have transfer rights the we are currently # talking to innd not nnrpd if ($obj->reader) { # If reader succeeds the we need to consider this code to determine postok $c = $obj->code; } else { # I want to ignore this failure, so restore the previous status. $obj->set_status($c, \@m); } } ${*$obj}{'net_nntp_post'} = $c == 200 ? 1 : 0; $obj; } sub host { my $me = shift; ${*$me}{'net_nntp_host'}; } sub debug_text { my $nntp = shift; my $inout = shift; my $text = shift; if ( (ref($nntp) and $nntp->code == 350 and $text =~ /^(\S+)/) || ($text =~ /^(authinfo\s+pass)/io)) { $text = "$1 ....\n"; } $text; } sub postok { @_ == 1 or croak 'usage: $nntp->postok()'; my $nntp = shift; ${*$nntp}{'net_nntp_post'} || 0; } sub starttls { my $self = shift; $ssl_class or die $nossl_warn; $self->_STARTTLS or return; Net::NNTP::_SSL->start_SSL($self, %{ ${*$self}{'net_nntp_arg'} }, # (ssl) args given in new @_ # more (ssl) args ) or return; return 1; } sub article { @_ >= 1 && @_ <= 3 or croak 'usage: $nntp->article( [ MSGID ], [ FH ] )'; my $nntp = shift; my @fh; @fh = (pop) if @_ == 2 || (@_ && (ref($_[0]) || ref(\$_[0]) eq 'GLOB')); $nntp->_ARTICLE(@_) ? $nntp->read_until_dot(@fh) : undef; } sub articlefh { @_ >= 1 && @_ <= 2 or croak 'usage: $nntp->articlefh( [ MSGID ] )'; my $nntp = shift; return unless $nntp->_ARTICLE(@_); return $nntp->tied_fh; } sub authinfo { @_ == 3 or croak 'usage: $nntp->authinfo( USER, PASS )'; my ($nntp, $user, $pass) = @_; $nntp->_AUTHINFO("USER", $user) == CMD_MORE && $nntp->_AUTHINFO("PASS", $pass) == CMD_OK; } sub authinfo_simple { @_ == 3 or croak 'usage: $nntp->authinfo( USER, PASS )'; my ($nntp, $user, $pass) = @_; $nntp->_AUTHINFO('SIMPLE') == CMD_MORE && $nntp->command($user, $pass)->response == CMD_OK; } sub body { @_ >= 1 && @_ <= 3 or croak 'usage: $nntp->body( [ MSGID ], [ FH ] )'; my $nntp = shift; my @fh; @fh = (pop) if @_ == 2 || (@_ && ref($_[0]) || ref(\$_[0]) eq 'GLOB'); $nntp->_BODY(@_) ? $nntp->read_until_dot(@fh) : undef; } sub bodyfh { @_ >= 1 && @_ <= 2 or croak 'usage: $nntp->bodyfh( [ MSGID ] )'; my $nntp = shift; return unless $nntp->_BODY(@_); return $nntp->tied_fh; } sub head { @_ >= 1 && @_ <= 3 or croak 'usage: $nntp->head( [ MSGID ], [ FH ] )'; my $nntp = shift; my @fh; @fh = (pop) if @_ == 2 || (@_ && ref($_[0]) || ref(\$_[0]) eq 'GLOB'); $nntp->_HEAD(@_) ? $nntp->read_until_dot(@fh) : undef; } sub headfh { @_ >= 1 && @_ <= 2 or croak 'usage: $nntp->headfh( [ MSGID ] )'; my $nntp = shift; return unless $nntp->_HEAD(@_); return $nntp->tied_fh; } sub nntpstat { @_ == 1 || @_ == 2 or croak 'usage: $nntp->nntpstat( [ MSGID ] )'; my $nntp = shift; $nntp->_STAT(@_) && $nntp->message =~ /(<[^>]+>)/o ? $1 : undef; } sub group { @_ == 1 || @_ == 2 or croak 'usage: $nntp->group( [ GROUP ] )'; my $nntp = shift; my $grp = ${*$nntp}{'net_nntp_group'}; return $grp unless (@_ || wantarray); my $newgrp = shift; $newgrp = (defined($grp) and length($grp)) ? $grp : "" unless defined($newgrp) and length($newgrp); return unless $nntp->_GROUP($newgrp) and $nntp->message =~ /(\d+)\s+(\d+)\s+(\d+)\s+(\S+)/; my ($count, $first, $last, $group) = ($1, $2, $3, $4); # group may be replied as '(current group)' $group = ${*$nntp}{'net_nntp_group'} if $group =~ /\(/; ${*$nntp}{'net_nntp_group'} = $group; wantarray ? ($count, $first, $last, $group) : $group; } sub help { @_ == 1 or croak 'usage: $nntp->help()'; my $nntp = shift; $nntp->_HELP ? $nntp->read_until_dot : undef; } sub ihave { @_ >= 2 or croak 'usage: $nntp->ihave( MESSAGE-ID [, MESSAGE ])'; my $nntp = shift; my $mid = shift; $nntp->_IHAVE($mid) && $nntp->datasend(@_) ? @_ == 0 || $nntp->dataend : undef; } sub last { @_ == 1 or croak 'usage: $nntp->last()'; my $nntp = shift; $nntp->_LAST && $nntp->message =~ /(<[^>]+>)/o ? $1 : undef; } sub list { @_ == 1 or croak 'usage: $nntp->list()'; my $nntp = shift; $nntp->_LIST ? $nntp->_grouplist : undef; } sub newgroups { @_ >= 2 or croak 'usage: $nntp->newgroups( SINCE [, DISTRIBUTIONS ])'; my $nntp = shift; my $time = _timestr(shift); my $dist = shift || ""; $dist = join(",", @{$dist}) if ref($dist); $nntp->_NEWGROUPS($time, $dist) ? $nntp->_grouplist : undef; } sub newnews { @_ >= 2 && @_ <= 4 or croak 'usage: $nntp->newnews( SINCE [, GROUPS [, DISTRIBUTIONS ]])'; my $nntp = shift; my $time = _timestr(shift); my $grp = @_ ? shift: $nntp->group; my $dist = shift || ""; $grp ||= "*"; $grp = join(",", @{$grp}) if ref($grp); $dist = join(",", @{$dist}) if ref($dist); $nntp->_NEWNEWS($grp, $time, $dist) ? $nntp->_articlelist : undef; } sub next { @_ == 1 or croak 'usage: $nntp->next()'; my $nntp = shift; $nntp->_NEXT && $nntp->message =~ /(<[^>]+>)/o ? $1 : undef; } sub post { @_ >= 1 or croak 'usage: $nntp->post( [ MESSAGE ] )'; my $nntp = shift; $nntp->_POST() && $nntp->datasend(@_) ? @_ == 0 || $nntp->dataend : undef; } sub postfh { my $nntp = shift; return unless $nntp->_POST(); return $nntp->tied_fh; } sub quit { @_ == 1 or croak 'usage: $nntp->quit()'; my $nntp = shift; $nntp->_QUIT; $nntp->close; } sub slave { @_ == 1 or croak 'usage: $nntp->slave()'; my $nntp = shift; $nntp->_SLAVE; } ## ## The following methods are not implemented by all servers ## sub active { @_ == 1 || @_ == 2 or croak 'usage: $nntp->active( [ PATTERN ] )'; my $nntp = shift; $nntp->_LIST('ACTIVE', @_) ? $nntp->_grouplist : undef; } sub active_times { @_ == 1 or croak 'usage: $nntp->active_times()'; my $nntp = shift; $nntp->_LIST('ACTIVE.TIMES') ? $nntp->_grouplist : undef; } sub distributions { @_ == 1 or croak 'usage: $nntp->distributions()'; my $nntp = shift; $nntp->_LIST('DISTRIBUTIONS') ? $nntp->_description : undef; } sub distribution_patterns { @_ == 1 or croak 'usage: $nntp->distributions()'; my $nntp = shift; my $arr; local $_; ## no critic (ControlStructures::ProhibitMutatingListFunctions) $nntp->_LIST('DISTRIB.PATS') && ($arr = $nntp->read_until_dot) ? [grep { /^\d/ && (chomp, $_ = [split /:/]) } @$arr] : undef; } sub newsgroups { @_ == 1 || @_ == 2 or croak 'usage: $nntp->newsgroups( [ PATTERN ] )'; my $nntp = shift; $nntp->_LIST('NEWSGROUPS', @_) ? $nntp->_description : undef; } sub overview_fmt { @_ == 1 or croak 'usage: $nntp->overview_fmt()'; my $nntp = shift; $nntp->_LIST('OVERVIEW.FMT') ? $nntp->_articlelist : undef; } sub subscriptions { @_ == 1 or croak 'usage: $nntp->subscriptions()'; my $nntp = shift; $nntp->_LIST('SUBSCRIPTIONS') ? $nntp->_articlelist : undef; } sub listgroup { @_ == 1 || @_ == 2 or croak 'usage: $nntp->listgroup( [ GROUP ] )'; my $nntp = shift; $nntp->_LISTGROUP(@_) ? $nntp->_articlelist : undef; } sub reader { @_ == 1 or croak 'usage: $nntp->reader()'; my $nntp = shift; $nntp->_MODE('READER'); } sub xgtitle { @_ == 1 || @_ == 2 or croak 'usage: $nntp->xgtitle( [ PATTERN ] )'; my $nntp = shift; $nntp->_XGTITLE(@_) ? $nntp->_description : undef; } sub xhdr { @_ >= 2 && @_ <= 4 or croak 'usage: $nntp->xhdr( HEADER, [ MESSAGE-SPEC ] )'; my $nntp = shift; my $hdr = shift; my $arg = _msg_arg(@_); $nntp->_XHDR($hdr, $arg) ? $nntp->_description : undef; } sub xover { @_ == 2 || @_ == 3 or croak 'usage: $nntp->xover( MESSAGE-SPEC )'; my $nntp = shift; my $arg = _msg_arg(@_); $nntp->_XOVER($arg) ? $nntp->_fieldlist : undef; } sub xpat { @_ == 4 || @_ == 5 or croak '$nntp->xpat( HEADER, PATTERN, MESSAGE-SPEC )'; my $nntp = shift; my $hdr = shift; my $pat = shift; my $arg = _msg_arg(@_); $pat = join(" ", @$pat) if ref($pat); $nntp->_XPAT($hdr, $arg, $pat) ? $nntp->_description : undef; } sub xpath { @_ == 2 or croak 'usage: $nntp->xpath( MESSAGE-ID )'; my ($nntp, $mid) = @_; return unless $nntp->_XPATH($mid); my $m; ($m = $nntp->message) =~ s/^\d+\s+//o; my @p = split /\s+/, $m; wantarray ? @p : $p[0]; } sub xrover { @_ == 2 || @_ == 3 or croak 'usage: $nntp->xrover( MESSAGE-SPEC )'; my $nntp = shift; my $arg = _msg_arg(@_); $nntp->_XROVER($arg) ? $nntp->_description : undef; } sub date { @_ == 1 or croak 'usage: $nntp->date()'; my $nntp = shift; $nntp->_DATE && $nntp->message =~ /(\d{4})(\d\d)(\d\d)(\d\d)(\d\d)(\d\d)/ ? timegm($6, $5, $4, $3, $2 - 1, $1 - 1900) : undef; } ## ## Private subroutines ## sub _msg_arg { my $spec = shift; my $arg = ""; if (@_) { carp "Depriciated passing of two message numbers, " . "pass a reference" if $^W; $spec = [$spec, $_[0]]; } if (defined $spec) { if (ref($spec)) { $arg = $spec->[0]; if (defined $spec->[1]) { $arg .= "-" if $spec->[1] != $spec->[0]; $arg .= $spec->[1] if $spec->[1] > $spec->[0]; } } else { $arg = $spec; } } $arg; } sub _timestr { my $time = shift; my @g = reverse((gmtime($time))[0 .. 5]); $g[1] += 1; $g[0] %= 100; sprintf "%02d%02d%02d %02d%02d%02d GMT", @g; } sub _grouplist { my $nntp = shift; my $arr = $nntp->read_until_dot or return; my $hash = {}; foreach my $ln (@$arr) { my @a = split(/[\s\n]+/, $ln); $hash->{$a[0]} = [@a[1, 2, 3]]; } $hash; } sub _fieldlist { my $nntp = shift; my $arr = $nntp->read_until_dot or return; my $hash = {}; foreach my $ln (@$arr) { my @a = split(/[\t\n]/, $ln); my $m = shift @a; $hash->{$m} = [@a]; } $hash; } sub _articlelist { my $nntp = shift; my $arr = $nntp->read_until_dot; chomp(@$arr) if $arr; $arr; } sub _description { my $nntp = shift; my $arr = $nntp->read_until_dot or return; my $hash = {}; foreach my $ln (@$arr) { chomp($ln); $hash->{$1} = $ln if $ln =~ s/^\s*(\S+)\s*//o; } $hash; } ## ## The commands ## sub _ARTICLE { shift->command('ARTICLE', @_)->response == CMD_OK } sub _AUTHINFO { shift->command('AUTHINFO', @_)->response } sub _BODY { shift->command('BODY', @_)->response == CMD_OK } sub _DATE { shift->command('DATE')->response == CMD_INFO } sub _GROUP { shift->command('GROUP', @_)->response == CMD_OK } sub _HEAD { shift->command('HEAD', @_)->response == CMD_OK } sub _HELP { shift->command('HELP', @_)->response == CMD_INFO } sub _IHAVE { shift->command('IHAVE', @_)->response == CMD_MORE } sub _LAST { shift->command('LAST')->response == CMD_OK } sub _LIST { shift->command('LIST', @_)->response == CMD_OK } sub _LISTGROUP { shift->command('LISTGROUP', @_)->response == CMD_OK } sub _NEWGROUPS { shift->command('NEWGROUPS', @_)->response == CMD_OK } sub _NEWNEWS { shift->command('NEWNEWS', @_)->response == CMD_OK } sub _NEXT { shift->command('NEXT')->response == CMD_OK } sub _POST { shift->command('POST', @_)->response == CMD_MORE } sub _QUIT { shift->command('QUIT', @_)->response == CMD_OK } sub _SLAVE { shift->command('SLAVE', @_)->response == CMD_OK } sub _STARTTLS { shift->command("STARTTLS")->response() == CMD_MORE } sub _STAT { shift->command('STAT', @_)->response == CMD_OK } sub _MODE { shift->command('MODE', @_)->response == CMD_OK } sub _XGTITLE { shift->command('XGTITLE', @_)->response == CMD_OK } sub _XHDR { shift->command('XHDR', @_)->response == CMD_OK } sub _XPAT { shift->command('XPAT', @_)->response == CMD_OK } sub _XPATH { shift->command('XPATH', @_)->response == CMD_OK } sub _XOVER { shift->command('XOVER', @_)->response == CMD_OK } sub _XROVER { shift->command('XROVER', @_)->response == CMD_OK } sub _XTHREAD { shift->unsupported } sub _XSEARCH { shift->unsupported } sub _XINDEX { shift->unsupported } ## ## IO/perl methods ## sub DESTROY { my $nntp = shift; defined(fileno($nntp)) && $nntp->quit; } { package Net::NNTP::_SSL; our @ISA = ( $ssl_class ? ($ssl_class):(), 'Net::NNTP' ); sub starttls { die "NNTP connection is already in SSL mode" } sub start_SSL { my ($class,$nntp,%arg) = @_; delete @arg{ grep { !m{^SSL_} } keys %arg }; ( $arg{SSL_verifycn_name} ||= $nntp->host ) =~s{(?can_client_sni; my $ok = $class->SUPER::start_SSL($nntp, SSL_verifycn_scheme => 'nntp', %arg ); $@ = $ssl_class->errstr if !$ok; return $ok; } } 1; __END__ # Net::FTP.pm # # Copyright (C) 1995-2004 Graham Barr. All rights reserved. # Copyright (C) 2013-2017 Steve Hay. All rights reserved. # This module is free software; you can redistribute it and/or modify it under # the same terms as Perl itself, i.e. under the terms of either the GNU General # Public License or the Artistic License, as specified in the F file. # # Documentation (at end) improved 1996 by Nathan Torkington . package Net::FTP; use 5.008001; use strict; use warnings; use Carp; use Fcntl qw(O_WRONLY O_RDONLY O_APPEND O_CREAT O_TRUNC); use IO::Socket; use Net::Cmd; use Net::Config; use Socket; use Time::Local; our $VERSION = '3.11'; our $IOCLASS; my $family_key; BEGIN { # Code for detecting if we can use SSL my $ssl_class = eval { require IO::Socket::SSL; # first version with default CA on most platforms no warnings 'numeric'; IO::Socket::SSL->VERSION(2.007); } && 'IO::Socket::SSL'; my $nossl_warn = !$ssl_class && 'To use SSL please install IO::Socket::SSL with version>=2.007'; # Code for detecting if we can use IPv6 my $inet6_class = eval { require IO::Socket::IP; no warnings 'numeric'; IO::Socket::IP->VERSION(0.25); } && 'IO::Socket::IP' || eval { require IO::Socket::INET6; no warnings 'numeric'; IO::Socket::INET6->VERSION(2.62); } && 'IO::Socket::INET6'; sub can_ssl { $ssl_class }; sub can_inet6 { $inet6_class }; $IOCLASS = $ssl_class || $inet6_class || 'IO::Socket::INET'; $family_key = ( $ssl_class ? $ssl_class->can_ipv6 : $inet6_class || '' ) eq 'IO::Socket::IP' ? 'Family' : 'Domain'; } our @ISA = ('Exporter','Net::Cmd',$IOCLASS); use constant TELNET_IAC => 255; use constant TELNET_IP => 244; use constant TELNET_DM => 242; use constant EBCDIC => $^O eq 'os390'; sub new { my $pkg = shift; my ($peer, %arg); if (@_ % 2) { $peer = shift; %arg = @_; } else { %arg = @_; $peer = delete $arg{Host}; } my $host = $peer; my $fire = undef; my $fire_type = undef; if (exists($arg{Firewall}) || Net::Config->requires_firewall($peer)) { $fire = $arg{Firewall} || $ENV{FTP_FIREWALL} || $NetConfig{ftp_firewall} || undef; if (defined $fire) { $peer = $fire; delete $arg{Port}; $fire_type = $arg{FirewallType} || $ENV{FTP_FIREWALL_TYPE} || $NetConfig{firewall_type} || undef; } } my %tlsargs; if (can_ssl()) { # for name verification strip port from domain:port, ipv4:port, [ipv6]:port (my $hostname = $host) =~s{(? 'ftp', SSL_verifycn_name => $hostname, # use SNI if supported by IO::Socket::SSL $pkg->can_client_sni ? (SSL_hostname => $hostname):(), # reuse SSL session of control connection in data connections SSL_session_cache => Net::FTP::_SSL_SingleSessionCache->new, ); # user defined SSL arg $tlsargs{$_} = $arg{$_} for(grep { m{^SSL_} } keys %arg); } elsif ($arg{SSL}) { croak("IO::Socket::SSL >= 2.007 needed for SSL support"); } my $ftp = $pkg->SUPER::new( PeerAddr => $peer, PeerPort => $arg{Port} || ($arg{SSL} ? 'ftps(990)' : 'ftp(21)'), LocalAddr => $arg{'LocalAddr'}, $family_key => $arg{Domain} || $arg{Family}, Proto => 'tcp', Timeout => defined $arg{Timeout} ? $arg{Timeout} : 120, %tlsargs, $arg{SSL} ? ():( SSL_startHandshake => 0 ), ) or return; ${*$ftp}{'net_ftp_host'} = $host; # Remote hostname ${*$ftp}{'net_ftp_type'} = 'A'; # ASCII/binary/etc mode ${*$ftp}{'net_ftp_blksize'} = abs($arg{'BlockSize'} || 10240); ${*$ftp}{'net_ftp_localaddr'} = $arg{'LocalAddr'}; ${*$ftp}{'net_ftp_domain'} = $arg{Domain} || $arg{Family}; ${*$ftp}{'net_ftp_firewall'} = $fire if (defined $fire); ${*$ftp}{'net_ftp_firewall_type'} = $fire_type if (defined $fire_type); ${*$ftp}{'net_ftp_passive'} = int exists $arg{Passive} ? $arg{Passive} : exists $ENV{FTP_PASSIVE} ? $ENV{FTP_PASSIVE} : defined $fire ? $NetConfig{ftp_ext_passive} : $NetConfig{ftp_int_passive}; # Whew! :-) ${*$ftp}{net_ftp_tlsargs} = \%tlsargs if %tlsargs; if ($arg{SSL}) { ${*$ftp}{net_ftp_tlsprot} = 'P'; ${*$ftp}{net_ftp_tlsdirect} = 1; } $ftp->hash(exists $arg{Hash} ? $arg{Hash} : 0, 1024); $ftp->autoflush(1); $ftp->debug(exists $arg{Debug} ? $arg{Debug} : undef); unless ($ftp->response() == CMD_OK) { $ftp->close(); # keep @$ if no message. Happens, when response did not start with a code. $@ = $ftp->message || $@; undef $ftp; } $ftp; } ## ## User interface methods ## sub host { my $me = shift; ${*$me}{'net_ftp_host'}; } sub passive { my $ftp = shift; return ${*$ftp}{'net_ftp_passive'} unless @_; ${*$ftp}{'net_ftp_passive'} = shift; } sub hash { my $ftp = shift; # self my ($h, $b) = @_; unless ($h) { delete ${*$ftp}{'net_ftp_hash'}; return [\*STDERR, 0]; } ($h, $b) = (ref($h) ? $h : \*STDERR, $b || 1024); select((select($h), $| = 1)[0]); $b = 512 if $b < 512; ${*$ftp}{'net_ftp_hash'} = [$h, $b]; } sub quit { my $ftp = shift; $ftp->_QUIT; $ftp->close; } sub DESTROY { } sub ascii { shift->type('A', @_); } sub binary { shift->type('I', @_); } sub ebcdic { carp "TYPE E is unsupported, shall default to I"; shift->type('E', @_); } sub byte { carp "TYPE L is unsupported, shall default to I"; shift->type('L', @_); } # Allow the user to send a command directly, BE CAREFUL !! sub quot { my $ftp = shift; my $cmd = shift; $ftp->command(uc $cmd, @_); $ftp->response(); } sub site { my $ftp = shift; $ftp->command("SITE", @_); $ftp->response(); } sub mdtm { my $ftp = shift; my $file = shift; # Server Y2K bug workaround # # sigh; some idiotic FTP servers use ("19%d",tm.tm_year) instead of # ("%d",tm.tm_year+1900). This results in an extra digit in the # string returned. To account for this we allow an optional extra # digit in the year. Then if the first two digits are 19 we use the # remainder, otherwise we subtract 1900 from the whole year. $ftp->_MDTM($file) && $ftp->message =~ /((\d\d)(\d\d\d?))(\d\d)(\d\d)(\d\d)(\d\d)(\d\d)/ ? timegm($8, $7, $6, $5, $4 - 1, $2 eq '19' ? $3 : ($1 - 1900)) : undef; } sub size { my $ftp = shift; my $file = shift; my $io; if ($ftp->supported("SIZE")) { return $ftp->_SIZE($file) ? ($ftp->message =~ /(\d+)\s*(bytes?\s*)?$/)[0] : undef; } elsif ($ftp->supported("STAT")) { my @msg; return unless $ftp->_STAT($file) && (@msg = $ftp->message) == 3; foreach my $line (@msg) { return (split(/\s+/, $line))[4] if $line =~ /^[-rwxSsTt]{10}/; } } else { my @files = $ftp->dir($file); if (@files) { return (split(/\s+/, $1))[4] if $files[0] =~ /^([-rwxSsTt]{10}.*)$/; } } undef; } sub starttls { my $ftp = shift; can_ssl() or croak("IO::Socket::SSL >= 2.007 needed for SSL support"); $ftp->is_SSL and croak("called starttls within SSL session"); $ftp->_AUTH('TLS') == CMD_OK or return; $ftp->connect_SSL or return; $ftp->prot('P'); return 1; } sub prot { my ($ftp,$prot) = @_; $prot eq 'C' or $prot eq 'P' or croak("prot must by C or P"); $ftp->_PBSZ(0) or return; $ftp->_PROT($prot) or return; ${*$ftp}{net_ftp_tlsprot} = $prot; return 1; } sub stoptls { my $ftp = shift; $ftp->is_SSL or croak("called stoptls outside SSL session"); ${*$ftp}{net_ftp_tlsdirect} and croak("cannot stoptls direct SSL session"); $ftp->_CCC() or return; $ftp->stop_SSL(); return 1; } sub login { my ($ftp, $user, $pass, $acct) = @_; my ($ok, $ruser, $fwtype); unless (defined $user) { require Net::Netrc; my $rc = Net::Netrc->lookup(${*$ftp}{'net_ftp_host'}); ($user, $pass, $acct) = $rc->lpa() if ($rc); } $user ||= "anonymous"; $ruser = $user; $fwtype = ${*$ftp}{'net_ftp_firewall_type'} || $NetConfig{'ftp_firewall_type'} || 0; if ($fwtype && defined ${*$ftp}{'net_ftp_firewall'}) { if ($fwtype == 1 || $fwtype == 7) { $user .= '@' . ${*$ftp}{'net_ftp_host'}; } else { require Net::Netrc; my $rc = Net::Netrc->lookup(${*$ftp}{'net_ftp_firewall'}); my ($fwuser, $fwpass, $fwacct) = $rc ? $rc->lpa() : (); if ($fwtype == 5) { $user = join('@', $user, $fwuser, ${*$ftp}{'net_ftp_host'}); $pass = $pass . '@' . $fwpass; } else { if ($fwtype == 2) { $user .= '@' . ${*$ftp}{'net_ftp_host'}; } elsif ($fwtype == 6) { $fwuser .= '@' . ${*$ftp}{'net_ftp_host'}; } $ok = $ftp->_USER($fwuser); return 0 unless $ok == CMD_OK || $ok == CMD_MORE; $ok = $ftp->_PASS($fwpass || ""); return 0 unless $ok == CMD_OK || $ok == CMD_MORE; $ok = $ftp->_ACCT($fwacct) if defined($fwacct); if ($fwtype == 3) { $ok = $ftp->command("SITE", ${*$ftp}{'net_ftp_host'})->response; } elsif ($fwtype == 4) { $ok = $ftp->command("OPEN", ${*$ftp}{'net_ftp_host'})->response; } return 0 unless $ok == CMD_OK || $ok == CMD_MORE; } } } $ok = $ftp->_USER($user); # Some dumb firewalls don't prefix the connection messages $ok = $ftp->response() if ($ok == CMD_OK && $ftp->code == 220 && $user =~ /\@/); if ($ok == CMD_MORE) { unless (defined $pass) { require Net::Netrc; my $rc = Net::Netrc->lookup(${*$ftp}{'net_ftp_host'}, $ruser); ($ruser, $pass, $acct) = $rc->lpa() if ($rc); $pass = '-anonymous@' if (!defined $pass && (!defined($ruser) || $ruser =~ /^anonymous/o)); } $ok = $ftp->_PASS($pass || ""); } $ok = $ftp->_ACCT($acct) if (defined($acct) && ($ok == CMD_MORE || $ok == CMD_OK)); if ($fwtype == 7 && $ok == CMD_OK && defined ${*$ftp}{'net_ftp_firewall'}) { my ($f, $auth, $resp) = _auth_id($ftp); $ftp->authorize($auth, $resp) if defined($resp); } $ok == CMD_OK; } sub account { @_ == 2 or croak 'usage: $ftp->account( ACCT )'; my $ftp = shift; my $acct = shift; $ftp->_ACCT($acct) == CMD_OK; } sub _auth_id { my ($ftp, $auth, $resp) = @_; unless (defined $resp) { require Net::Netrc; $auth ||= eval { (getpwuid($>))[0] } || $ENV{NAME}; my $rc = Net::Netrc->lookup(${*$ftp}{'net_ftp_firewall'}, $auth) || Net::Netrc->lookup(${*$ftp}{'net_ftp_firewall'}); ($auth, $resp) = $rc->lpa() if ($rc); } ($ftp, $auth, $resp); } sub authorize { @_ >= 1 || @_ <= 3 or croak 'usage: $ftp->authorize( [AUTH [, RESP]])'; my ($ftp, $auth, $resp) = &_auth_id; my $ok = $ftp->_AUTH($auth || ""); return $ftp->_RESP($resp || "") if ($ok == CMD_MORE); $ok == CMD_OK; } sub rename { @_ == 3 or croak 'usage: $ftp->rename(FROM, TO)'; my ($ftp, $from, $to) = @_; $ftp->_RNFR($from) && $ftp->_RNTO($to); } sub type { my $ftp = shift; my $type = shift; my $oldval = ${*$ftp}{'net_ftp_type'}; return $oldval unless (defined $type); return unless ($ftp->_TYPE($type, @_)); ${*$ftp}{'net_ftp_type'} = join(" ", $type, @_); $oldval; } sub alloc { my $ftp = shift; my $size = shift; my $oldval = ${*$ftp}{'net_ftp_allo'}; return $oldval unless (defined $size); return unless ($ftp->supported("ALLO") and $ftp->_ALLO($size, @_)); ${*$ftp}{'net_ftp_allo'} = join(" ", $size, @_); $oldval; } sub abort { my $ftp = shift; send($ftp, pack("CCC", TELNET_IAC, TELNET_IP, TELNET_IAC), MSG_OOB); $ftp->command(pack("C", TELNET_DM) . "ABOR"); ${*$ftp}{'net_ftp_dataconn'}->close() if defined ${*$ftp}{'net_ftp_dataconn'}; $ftp->response(); $ftp->status == CMD_OK; } sub get { my ($ftp, $remote, $local, $where) = @_; my ($loc, $len, $buf, $resp, $data); local *FD; my $localfd = ref($local) || ref(\$local) eq "GLOB"; ($local = $remote) =~ s#^.*/## unless (defined $local); croak("Bad remote filename '$remote'\n") if $remote =~ /[\r\n]/s; ${*$ftp}{'net_ftp_rest'} = $where if defined $where; my $rest = ${*$ftp}{'net_ftp_rest'}; delete ${*$ftp}{'net_ftp_port'}; delete ${*$ftp}{'net_ftp_pasv'}; $data = $ftp->retr($remote) or return; if ($localfd) { $loc = $local; } else { $loc = \*FD; unless (sysopen($loc, $local, O_CREAT | O_WRONLY | ($rest ? O_APPEND: O_TRUNC))) { carp "Cannot open Local file $local: $!\n"; $data->abort; return; } } if ($ftp->type eq 'I' && !binmode($loc)) { carp "Cannot binmode Local file $local: $!\n"; $data->abort; close($loc) unless $localfd; return; } $buf = ''; my ($count, $hashh, $hashb, $ref) = (0); ($hashh, $hashb) = @$ref if ($ref = ${*$ftp}{'net_ftp_hash'}); my $blksize = ${*$ftp}{'net_ftp_blksize'}; local $\; # Just in case while (1) { last unless $len = $data->read($buf, $blksize); if (EBCDIC && $ftp->type ne 'I') { $buf = $ftp->toebcdic($buf); $len = length($buf); } if ($hashh) { $count += $len; print $hashh "#" x (int($count / $hashb)); $count %= $hashb; } unless (print $loc $buf) { carp "Cannot write to Local file $local: $!\n"; $data->abort; close($loc) unless $localfd; return; } } print $hashh "\n" if $hashh; unless ($localfd) { unless (close($loc)) { carp "Cannot close file $local (perhaps disk space) $!\n"; return; } } unless ($data->close()) # implied $ftp->response { carp "Unable to close datastream"; return; } return $local; } sub cwd { @_ == 1 || @_ == 2 or croak 'usage: $ftp->cwd( [ DIR ] )'; my ($ftp, $dir) = @_; $dir = "/" unless defined($dir) && $dir =~ /\S/; $dir eq ".." ? $ftp->_CDUP() : $ftp->_CWD($dir); } sub cdup { @_ == 1 or croak 'usage: $ftp->cdup()'; $_[0]->_CDUP; } sub pwd { @_ == 1 || croak 'usage: $ftp->pwd()'; my $ftp = shift; $ftp->_PWD(); $ftp->_extract_path; } # rmdir( $ftp, $dir, [ $recurse ] ) # # Removes $dir on remote host via FTP. # $ftp is handle for remote host # # If $recurse is TRUE, the directory and deleted recursively. # This means all of its contents and subdirectories. # # Initial version contributed by Dinkum Software # sub rmdir { @_ == 2 || @_ == 3 or croak('usage: $ftp->rmdir( DIR [, RECURSE ] )'); # Pick off the args my ($ftp, $dir, $recurse) = @_; my $ok; return $ok if $ok = $ftp->_RMD($dir) or !$recurse; # Try to delete the contents # Get a list of all the files in the directory, excluding the current and parent directories my @filelist = map { /^(?:\S+;)+ (.+)$/ ? ($1) : () } grep { !/^(?:\S+;)*type=[cp]dir;/i } $ftp->_list_cmd("MLSD", $dir); # Fallback to using the less well-defined NLST command if MLSD fails @filelist = grep { !/^\.{1,2}$/ } $ftp->ls($dir) unless @filelist; return unless @filelist; # failed, it is probably not a directory return $ftp->delete($dir) if @filelist == 1 and $dir eq $filelist[0]; # Go thru and delete each file or the directory foreach my $file (map { m,/, ? $_ : "$dir/$_" } @filelist) { next # successfully deleted the file if $ftp->delete($file); # Failed to delete it, assume its a directory # Recurse and ignore errors, the final rmdir() will # fail on any errors here return $ok unless $ok = $ftp->rmdir($file, 1); } # Directory should be empty # Try to remove the directory again # Pass results directly to caller # If any of the prior deletes failed, this # rmdir() will fail because directory is not empty return $ftp->_RMD($dir); } sub restart { @_ == 2 || croak 'usage: $ftp->restart( BYTE_OFFSET )'; my ($ftp, $where) = @_; ${*$ftp}{'net_ftp_rest'} = $where; return; } sub mkdir { @_ == 2 || @_ == 3 or croak 'usage: $ftp->mkdir( DIR [, RECURSE ] )'; my ($ftp, $dir, $recurse) = @_; $ftp->_MKD($dir) || $recurse or return; my $path = $dir; unless ($ftp->ok) { my @path = split(m#(?=/+)#, $dir); $path = ""; while (@path) { $path .= shift @path; $ftp->_MKD($path); $path = $ftp->_extract_path($path); } # If the creation of the last element was not successful, see if we # can cd to it, if so then return path unless ($ftp->ok) { my ($status, $message) = ($ftp->status, $ftp->message); my $pwd = $ftp->pwd; if ($pwd && $ftp->cwd($dir)) { $path = $dir; $ftp->cwd($pwd); } else { undef $path; } $ftp->set_status($status, $message); } } $path; } sub delete { @_ == 2 || croak 'usage: $ftp->delete( FILENAME )'; $_[0]->_DELE($_[1]); } sub put { shift->_store_cmd("stor", @_) } sub put_unique { shift->_store_cmd("stou", @_) } sub append { shift->_store_cmd("appe", @_) } sub nlst { shift->_data_cmd("NLST", @_) } sub list { shift->_data_cmd("LIST", @_) } sub retr { shift->_data_cmd("RETR", @_) } sub stor { shift->_data_cmd("STOR", @_) } sub stou { shift->_data_cmd("STOU", @_) } sub appe { shift->_data_cmd("APPE", @_) } sub _store_cmd { my ($ftp, $cmd, $local, $remote) = @_; my ($loc, $sock, $len, $buf); local *FD; my $localfd = ref($local) || ref(\$local) eq "GLOB"; if (!defined($remote) and 'STOU' ne uc($cmd)) { croak 'Must specify remote filename with stream input' if $localfd; require File::Basename; $remote = File::Basename::basename($local); } if (defined ${*$ftp}{'net_ftp_allo'}) { delete ${*$ftp}{'net_ftp_allo'}; } else { # if the user hasn't already invoked the alloc method since the last # _store_cmd call, figure out if the local file is a regular file(not # a pipe, or device) and if so get the file size from stat, and send # an ALLO command before sending the STOR, STOU, or APPE command. my $size = do { local $^W; -f $local && -s _ }; # no ALLO if sending data from a pipe ${*$ftp}{'net_ftp_allo'} = $size if $size; } croak("Bad remote filename '$remote'\n") if defined($remote) and $remote =~ /[\r\n]/s; if ($localfd) { $loc = $local; } else { $loc = \*FD; unless (sysopen($loc, $local, O_RDONLY)) { carp "Cannot open Local file $local: $!\n"; return; } } if ($ftp->type eq 'I' && !binmode($loc)) { carp "Cannot binmode Local file $local: $!\n"; return; } delete ${*$ftp}{'net_ftp_port'}; delete ${*$ftp}{'net_ftp_pasv'}; $sock = $ftp->_data_cmd($cmd, grep { defined } $remote) or return; $remote = ($ftp->message =~ /\w+\s*:\s*(.*)/)[0] if 'STOU' eq uc $cmd; my $blksize = ${*$ftp}{'net_ftp_blksize'}; my ($count, $hashh, $hashb, $ref) = (0); ($hashh, $hashb) = @$ref if ($ref = ${*$ftp}{'net_ftp_hash'}); while (1) { last unless $len = read($loc, $buf = "", $blksize); if (EBCDIC && $ftp->type ne 'I') { $buf = $ftp->toascii($buf); $len = length($buf); } if ($hashh) { $count += $len; print $hashh "#" x (int($count / $hashb)); $count %= $hashb; } my $wlen; unless (defined($wlen = $sock->write($buf, $len)) && $wlen == $len) { $sock->abort; close($loc) unless $localfd; print $hashh "\n" if $hashh; return; } } print $hashh "\n" if $hashh; close($loc) unless $localfd; $sock->close() or return; if ('STOU' eq uc $cmd and $ftp->message =~ m/unique\s+file\s*name\s*:\s*(.*)\)|"(.*)"/) { require File::Basename; $remote = File::Basename::basename($+); } return $remote; } sub port { @_ == 1 || @_ == 2 or croak 'usage: $self->port([PORT])'; return _eprt('PORT',@_); } sub eprt { @_ == 1 || @_ == 2 or croak 'usage: $self->eprt([PORT])'; return _eprt('EPRT',@_); } sub _eprt { my ($cmd,$ftp,$port) = @_; delete ${*$ftp}{net_ftp_intern_port}; unless ($port) { my $listen = ${*$ftp}{net_ftp_listen} ||= $IOCLASS->new( Listen => 1, Timeout => $ftp->timeout, LocalAddr => $ftp->sockhost, $family_key => $ftp->sockdomain, can_ssl() ? ( %{ ${*$ftp}{net_ftp_tlsargs} }, SSL_startHandshake => 0, ):(), ); ${*$ftp}{net_ftp_intern_port} = 1; my $fam = ($listen->sockdomain == AF_INET) ? 1:2; if ( $cmd eq 'EPRT' || $fam == 2 ) { $port = "|$fam|".$listen->sockhost."|".$listen->sockport."|"; $cmd = 'EPRT'; } else { my $p = $listen->sockport; $port = join(',',split(m{\.},$listen->sockhost),$p >> 8,$p & 0xff); } } elsif (ref($port) eq 'ARRAY') { $port = join(',',split(m{\.},@$port[0]),@$port[1] >> 8,@$port[1] & 0xff); } my $ok = $cmd eq 'EPRT' ? $ftp->_EPRT($port) : $ftp->_PORT($port); ${*$ftp}{net_ftp_port} = $port if $ok; return $ok; } sub ls { shift->_list_cmd("NLST", @_); } sub dir { shift->_list_cmd("LIST", @_); } sub pasv { my $ftp = shift; @_ and croak 'usage: $ftp->port()'; return $ftp->epsv if $ftp->sockdomain != AF_INET; delete ${*$ftp}{net_ftp_intern_port}; if ( $ftp->_PASV && $ftp->message =~ m{(\d+,\d+,\d+,\d+),(\d+),(\d+)} ) { my $port = 256 * $2 + $3; ( my $ip = $1 ) =~s{,}{.}g; return ${*$ftp}{net_ftp_pasv} = [ $ip,$port ]; } return; } sub epsv { my $ftp = shift; @_ and croak 'usage: $ftp->epsv()'; delete ${*$ftp}{net_ftp_intern_port}; $ftp->_EPSV && $ftp->message =~ m{\(([\x33-\x7e])\1\1(\d+)\1\)} ? ${*$ftp}{net_ftp_pasv} = [ $ftp->peerhost, $2 ] : undef; } sub unique_name { my $ftp = shift; ${*$ftp}{'net_ftp_unique'} || undef; } sub supported { @_ == 2 or croak 'usage: $ftp->supported( CMD )'; my $ftp = shift; my $cmd = uc shift; my $hash = ${*$ftp}{'net_ftp_supported'} ||= {}; return $hash->{$cmd} if exists $hash->{$cmd}; return $hash->{$cmd} = 1 if $ftp->feature($cmd); return $hash->{$cmd} = 0 unless $ftp->_HELP($cmd); my $text = $ftp->message; if ($text =~ /following.+commands/i) { $text =~ s/^.*\n//; while ($text =~ /(\*?)(\w+)(\*?)/sg) { $hash->{"\U$2"} = !length("$1$3"); } } else { $hash->{$cmd} = $text !~ /unimplemented/i; } $hash->{$cmd} ||= 0; } ## ## Deprecated methods ## sub lsl { carp "Use of Net::FTP::lsl deprecated, use 'dir'" if $^W; goto &dir; } sub authorise { carp "Use of Net::FTP::authorise deprecated, use 'authorize'" if $^W; goto &authorize; } ## ## Private methods ## sub _extract_path { my ($ftp, $path) = @_; # This tries to work both with and without the quote doubling # convention (RFC 959 requires it, but the first 3 servers I checked # didn't implement it). It will fail on a server which uses a quote in # the message which isn't a part of or surrounding the path. $ftp->ok && $ftp->message =~ /(?:^|\s)\"(.*)\"(?:$|\s)/ && ($path = $1) =~ s/\"\"/\"/g; $path; } ## ## Communication methods ## sub _dataconn { my $ftp = shift; my $pkg = "Net::FTP::" . $ftp->type; eval "require " . $pkg ## no critic (BuiltinFunctions::ProhibitStringyEval) or croak("cannot load $pkg required for type ".$ftp->type); $pkg =~ s/ /_/g; delete ${*$ftp}{net_ftp_dataconn}; my $conn; my $pasv = ${*$ftp}{net_ftp_pasv}; if ($pasv) { $conn = $pkg->new( PeerAddr => $pasv->[0], PeerPort => $pasv->[1], LocalAddr => ${*$ftp}{net_ftp_localaddr}, $family_key => ${*$ftp}{net_ftp_domain}, Timeout => $ftp->timeout, can_ssl() ? ( SSL_startHandshake => 0, $ftp->is_SSL ? ( SSL_reuse_ctx => $ftp, SSL_verifycn_name => ${*$ftp}{net_ftp_tlsargs}{SSL_verifycn_name}, # This will cause the use of SNI if supported by IO::Socket::SSL. $ftp->can_client_sni ? ( SSL_hostname => ${*$ftp}{net_ftp_tlsargs}{SSL_hostname} ):(), ) :( %{${*$ftp}{net_ftp_tlsargs}} ), ):(), ) or return; } elsif (my $listen = delete ${*$ftp}{net_ftp_listen}) { $conn = $listen->accept($pkg) or return; $conn->timeout($ftp->timeout); close($listen); } else { croak("no listener in active mode"); } if (( ${*$ftp}{net_ftp_tlsprot} || '') eq 'P') { if ($conn->connect_SSL) { # SSL handshake ok } else { carp("failed to ssl upgrade dataconn: $IO::Socket::SSL::SSL_ERROR"); return; } } ${*$ftp}{net_ftp_dataconn} = $conn; ${*$conn} = ""; ${*$conn}{net_ftp_cmd} = $ftp; ${*$conn}{net_ftp_blksize} = ${*$ftp}{net_ftp_blksize}; return $conn; } sub _list_cmd { my $ftp = shift; my $cmd = uc shift; delete ${*$ftp}{'net_ftp_port'}; delete ${*$ftp}{'net_ftp_pasv'}; my $data = $ftp->_data_cmd($cmd, @_); return unless (defined $data); require Net::FTP::A; bless $data, "Net::FTP::A"; # Force ASCII mode my $databuf = ''; my $buf = ''; my $blksize = ${*$ftp}{'net_ftp_blksize'}; while ($data->read($databuf, $blksize)) { $buf .= $databuf; } my $list = [split(/\n/, $buf)]; $data->close(); if (EBCDIC) { for (@$list) { $_ = $ftp->toebcdic($_) } } wantarray ? @{$list} : $list; } sub _data_cmd { my $ftp = shift; my $cmd = uc shift; my $ok = 1; my $where = delete ${*$ftp}{'net_ftp_rest'} || 0; my $arg; for my $arg (@_) { croak("Bad argument '$arg'\n") if $arg =~ /[\r\n]/s; } if ( ${*$ftp}{'net_ftp_passive'} && !defined ${*$ftp}{'net_ftp_pasv'} && !defined ${*$ftp}{'net_ftp_port'}) { return unless defined $ftp->pasv; if ($where and !$ftp->_REST($where)) { my ($status, $message) = ($ftp->status, $ftp->message); $ftp->abort; $ftp->set_status($status, $message); return; } # first send command, then open data connection # otherwise the peer might not do a full accept (with SSL # handshake if PROT P) $ftp->command($cmd, @_); my $data = $ftp->_dataconn(); if (CMD_INFO == $ftp->response()) { $data->reading if $data && $cmd =~ /RETR|LIST|NLST|MLSD/; return $data; } $data->_close if $data; return; } $ok = $ftp->port unless (defined ${*$ftp}{'net_ftp_port'} || defined ${*$ftp}{'net_ftp_pasv'}); $ok = $ftp->_REST($where) if $ok && $where; return unless $ok; if ($cmd =~ /(STOR|APPE|STOU)/ and exists ${*$ftp}{net_ftp_allo} and $ftp->supported("ALLO")) { $ftp->_ALLO(delete ${*$ftp}{net_ftp_allo}) or return; } $ftp->command($cmd, @_); return 1 if (defined ${*$ftp}{'net_ftp_pasv'}); $ok = CMD_INFO == $ftp->response(); return $ok unless exists ${*$ftp}{'net_ftp_intern_port'}; if ($ok) { my $data = $ftp->_dataconn(); $data->reading if $data && $cmd =~ /RETR|LIST|NLST|MLSD/; return $data; } close(delete ${*$ftp}{'net_ftp_listen'}); return; } ## ## Over-ride methods (Net::Cmd) ## sub debug_text { $_[2] =~ /^(pass|resp|acct)/i ? "$1 ....\n" : $_[2]; } sub command { my $ftp = shift; delete ${*$ftp}{'net_ftp_port'}; $ftp->SUPER::command(@_); } sub response { my $ftp = shift; my $code = $ftp->SUPER::response() || 5; # assume 500 if undef delete ${*$ftp}{'net_ftp_pasv'} if ($code != CMD_MORE && $code != CMD_INFO); $code; } sub parse_response { return ($1, $2 eq "-") if $_[1] =~ s/^(\d\d\d)([- ]?)//o; my $ftp = shift; # Darn MS FTP server is a load of CRAP !!!! # Expect to see undef here. return () unless 0 + (${*$ftp}{'net_cmd_code'} || 0); (${*$ftp}{'net_cmd_code'}, 1); } ## ## Allow 2 servers to talk directly ## sub pasv_xfer_unique { my ($sftp, $sfile, $dftp, $dfile) = @_; $sftp->pasv_xfer($sfile, $dftp, $dfile, 1); } sub pasv_xfer { my ($sftp, $sfile, $dftp, $dfile, $unique) = @_; ($dfile = $sfile) =~ s#.*/## unless (defined $dfile); my $port = $sftp->pasv or return; $dftp->port($port) or return; return unless ($unique ? $dftp->stou($dfile) : $dftp->stor($dfile)); unless ($sftp->retr($sfile) && $sftp->response == CMD_INFO) { $sftp->retr($sfile); $dftp->abort; $dftp->response(); return; } $dftp->pasv_wait($sftp); } sub pasv_wait { @_ == 2 or croak 'usage: $ftp->pasv_wait(NON_PASV_FTP)'; my ($ftp, $non_pasv) = @_; my ($file, $rin, $rout); vec($rin = '', fileno($ftp), 1) = 1; select($rout = $rin, undef, undef, undef); my $dres = $ftp->response(); my $sres = $non_pasv->response(); return unless $dres == CMD_OK && $sres == CMD_OK; return unless $ftp->ok() && $non_pasv->ok(); return $1 if $ftp->message =~ /unique file name:\s*(\S*)\s*\)/; return $1 if $non_pasv->message =~ /unique file name:\s*(\S*)\s*\)/; return 1; } sub feature { @_ == 2 or croak 'usage: $ftp->feature( NAME )'; my ($ftp, $feat) = @_; my $feature = ${*$ftp}{net_ftp_feature} ||= do { my @feat; # Example response # 211-Features: # MDTM # REST STREAM # SIZE # 211 End @feat = map { /^\s+(.*\S)/ } $ftp->message if $ftp->_FEAT; \@feat; }; return grep { /^\Q$feat\E\b/i } @$feature; } sub cmd { shift->command(@_)->response() } ######################################## # # RFC959 + RFC2428 + RFC4217 commands # sub _ABOR { shift->command("ABOR")->response() == CMD_OK } sub _ALLO { shift->command("ALLO", @_)->response() == CMD_OK } sub _CDUP { shift->command("CDUP")->response() == CMD_OK } sub _NOOP { shift->command("NOOP")->response() == CMD_OK } sub _PASV { shift->command("PASV")->response() == CMD_OK } sub _QUIT { shift->command("QUIT")->response() == CMD_OK } sub _DELE { shift->command("DELE", @_)->response() == CMD_OK } sub _CWD { shift->command("CWD", @_)->response() == CMD_OK } sub _PORT { shift->command("PORT", @_)->response() == CMD_OK } sub _RMD { shift->command("RMD", @_)->response() == CMD_OK } sub _MKD { shift->command("MKD", @_)->response() == CMD_OK } sub _PWD { shift->command("PWD", @_)->response() == CMD_OK } sub _TYPE { shift->command("TYPE", @_)->response() == CMD_OK } sub _RNTO { shift->command("RNTO", @_)->response() == CMD_OK } sub _RESP { shift->command("RESP", @_)->response() == CMD_OK } sub _MDTM { shift->command("MDTM", @_)->response() == CMD_OK } sub _SIZE { shift->command("SIZE", @_)->response() == CMD_OK } sub _HELP { shift->command("HELP", @_)->response() == CMD_OK } sub _STAT { shift->command("STAT", @_)->response() == CMD_OK } sub _FEAT { shift->command("FEAT", @_)->response() == CMD_OK } sub _PBSZ { shift->command("PBSZ", @_)->response() == CMD_OK } sub _PROT { shift->command("PROT", @_)->response() == CMD_OK } sub _CCC { shift->command("CCC", @_)->response() == CMD_OK } sub _EPRT { shift->command("EPRT", @_)->response() == CMD_OK } sub _EPSV { shift->command("EPSV", @_)->response() == CMD_OK } sub _APPE { shift->command("APPE", @_)->response() == CMD_INFO } sub _LIST { shift->command("LIST", @_)->response() == CMD_INFO } sub _NLST { shift->command("NLST", @_)->response() == CMD_INFO } sub _RETR { shift->command("RETR", @_)->response() == CMD_INFO } sub _STOR { shift->command("STOR", @_)->response() == CMD_INFO } sub _STOU { shift->command("STOU", @_)->response() == CMD_INFO } sub _RNFR { shift->command("RNFR", @_)->response() == CMD_MORE } sub _REST { shift->command("REST", @_)->response() == CMD_MORE } sub _PASS { shift->command("PASS", @_)->response() } sub _ACCT { shift->command("ACCT", @_)->response() } sub _AUTH { shift->command("AUTH", @_)->response() } sub _USER { my $ftp = shift; my $ok = $ftp->command("USER", @_)->response(); # A certain brain dead firewall :-) $ok = $ftp->command("user", @_)->response() unless $ok == CMD_MORE or $ok == CMD_OK; $ok; } sub _SMNT { shift->unsupported(@_) } sub _MODE { shift->unsupported(@_) } sub _SYST { shift->unsupported(@_) } sub _STRU { shift->unsupported(@_) } sub _REIN { shift->unsupported(@_) } { # Session Cache with single entry # used to make sure that we reuse same session for control and data channels package Net::FTP::_SSL_SingleSessionCache; sub new { my $x; return bless \$x,shift } sub add_session { my ($cache,$key,$session) = @_; Net::SSLeay::SESSION_free($$cache) if $$cache; $$cache = $session; } sub get_session { my $cache = shift; return $$cache } sub DESTROY { my $cache = shift; Net::SSLeay::SESSION_free($$cache) if $$cache; } } 1; __END__ # Net::Netrc.pm # # Copyright (C) 1995-1998 Graham Barr. All rights reserved. # Copyright (C) 2013-2014 Steve Hay. All rights reserved. # This module is free software; you can redistribute it and/or modify it under # the same terms as Perl itself, i.e. under the terms of either the GNU General # Public License or the Artistic License, as specified in the F file. package Net::Netrc; use 5.008001; use strict; use warnings; use Carp; use FileHandle; our $VERSION = "3.11"; our $TESTING; my %netrc = (); sub _readrc { my($class, $host) = @_; my ($home, $file); if ($^O eq "MacOS") { $home = $ENV{HOME} || `pwd`; chomp($home); $file = ($home =~ /:$/ ? $home . "netrc" : $home . ":netrc"); } else { # Some OS's don't have "getpwuid", so we default to $ENV{HOME} $home = eval { (getpwuid($>))[7] } || $ENV{HOME}; $home ||= $ENV{HOMEDRIVE} . ($ENV{HOMEPATH} || '') if defined $ENV{HOMEDRIVE}; if (-e $home . "/.netrc") { $file = $home . "/.netrc"; } elsif (-e $home . "/_netrc") { $file = $home . "/_netrc"; } else { return unless $TESTING; } } my ($login, $pass, $acct) = (undef, undef, undef); my $fh; local $_; $netrc{default} = undef; # OS/2 and Win32 do not handle stat in a way compatible with this check :-( unless ($^O eq 'os2' || $^O eq 'MSWin32' || $^O eq 'MacOS' || $^O =~ /^cygwin/) { my @stat = stat($file); if (@stat) { if ($stat[2] & 077) { ## no critic (ValuesAndExpressions::ProhibitLeadingZeros) carp "Bad permissions: $file"; return; } if ($stat[4] != $<) { carp "Not owner: $file"; return; } } } if ($fh = FileHandle->new($file, "r")) { my ($mach, $macdef, $tok, @tok) = (0, 0); while (<$fh>) { undef $macdef if /\A\n\Z/; if ($macdef) { push(@$macdef, $_); next; } s/^\s*//; chomp; while (length && s/^("((?:[^"]+|\\.)*)"|((?:[^\\\s]+|\\.)*))\s*//) { (my $tok = $+) =~ s/\\(.)/$1/g; push(@tok, $tok); } TOKEN: while (@tok) { if ($tok[0] eq "default") { shift(@tok); $mach = bless {}, $class; $netrc{default} = [$mach]; next TOKEN; } last TOKEN unless @tok > 1; $tok = shift(@tok); if ($tok eq "machine") { my $host = shift @tok; $mach = bless {machine => $host}, $class; $netrc{$host} = [] unless exists($netrc{$host}); push(@{$netrc{$host}}, $mach); } elsif ($tok =~ /^(login|password|account)$/) { next TOKEN unless $mach; my $value = shift @tok; # Following line added by rmerrell to remove '/' escape char in .netrc $value =~ s/\/\\/\\/g; $mach->{$1} = $value; } elsif ($tok eq "macdef") { next TOKEN unless $mach; my $value = shift @tok; $mach->{macdef} = {} unless exists $mach->{macdef}; $macdef = $mach->{machdef}{$value} = []; } } } $fh->close(); } } sub lookup { my ($class, $mach, $login) = @_; $class->_readrc() unless exists $netrc{default}; $mach ||= 'default'; undef $login if $mach eq 'default'; if (exists $netrc{$mach}) { if (defined $login) { foreach my $m (@{$netrc{$mach}}) { return $m if (exists $m->{login} && $m->{login} eq $login); } return; } return $netrc{$mach}->[0]; } return $netrc{default}->[0] if defined $netrc{default}; return; } sub login { my $me = shift; exists $me->{login} ? $me->{login} : undef; } sub account { my $me = shift; exists $me->{account} ? $me->{account} : undef; } sub password { my $me = shift; exists $me->{password} ? $me->{password} : undef; } sub lpa { my $me = shift; ($me->login, $me->password, $me->account); } 1; __END__ # Net::Config.pm # # Copyright (C) 2000 Graham Barr. All rights reserved. # Copyright (C) 2013-2014, 2016 Steve Hay. All rights reserved. # This module is free software; you can redistribute it and/or modify it under # the same terms as Perl itself, i.e. under the terms of either the GNU General # Public License or the Artistic License, as specified in the F file. package Net::Config; use 5.008001; use strict; use warnings; use Exporter; use Socket qw(inet_aton inet_ntoa); our @EXPORT = qw(%NetConfig); our @ISA = qw(Net::LocalCfg Exporter); our $VERSION = "3.11"; our($CONFIGURE, $LIBNET_CFG); eval { local @INC = @INC; pop @INC if $INC[-1] eq '.'; local $SIG{__DIE__}; require Net::LocalCfg; }; our %NetConfig = ( nntp_hosts => [], snpp_hosts => [], pop3_hosts => [], smtp_hosts => [], ph_hosts => [], daytime_hosts => [], time_hosts => [], inet_domain => undef, ftp_firewall => undef, ftp_ext_passive => 1, ftp_int_passive => 1, test_hosts => 1, test_exist => 1, ); # # Try to get as much configuration info as possible from InternetConfig # { ## no critic (BuiltinFunctions::ProhibitStringyEval) $^O eq 'MacOS' and eval < [ \$InternetConfig{ kICNNTPHost() } ], pop3_hosts => [ \$InternetConfig{ kICMailAccount() } =~ /\@(.*)/ ], smtp_hosts => [ \$InternetConfig{ kICSMTPHost() } ], ftp_testhost => \$InternetConfig{ kICFTPHost() } ? \$InternetConfig{ kICFTPHost()} : undef, ph_hosts => [ \$InternetConfig{ kICPhHost() } ], ftp_ext_passive => \$InternetConfig{"646F676F\xA5UsePassiveMode"} || 0, ftp_int_passive => \$InternetConfig{"646F676F\xA5UsePassiveMode"} || 0, socks_hosts => \$InternetConfig{ kICUseSocks() } ? [ \$InternetConfig{ kICSocksHost() } ] : [], ftp_firewall => \$InternetConfig{ kICUseFTPProxy() } ? [ \$InternetConfig{ kICFTPProxyHost() } ] : [], ); \@NetConfig{keys %nc} = values %nc; } TRY_INTERNET_CONFIG } my $file = __FILE__; my $ref; $file =~ s/Config.pm/libnet.cfg/; if (-f $file) { $ref = eval { local $SIG{__DIE__}; do $file }; if (ref($ref) eq 'HASH') { %NetConfig = (%NetConfig, %{$ref}); $LIBNET_CFG = $file; } } if ($< == $> and !$CONFIGURE) { my $home = eval { local $SIG{__DIE__}; (getpwuid($>))[7] } || $ENV{HOME}; $home ||= $ENV{HOMEDRIVE} . ($ENV{HOMEPATH} || '') if defined $ENV{HOMEDRIVE}; if (defined $home) { $file = $home . "/.libnetrc"; $ref = eval { local $SIG{__DIE__}; do $file } if -f $file; %NetConfig = (%NetConfig, %{$ref}) if ref($ref) eq 'HASH'; } } my ($k, $v); while (($k, $v) = each %NetConfig) { $NetConfig{$k} = [$v] if ($k =~ /_hosts$/ and $k ne "test_hosts" and defined($v) and !ref($v)); } # Take a hostname and determine if it is inside the firewall sub requires_firewall { shift; # ignore package my $host = shift; return 0 unless defined $NetConfig{'ftp_firewall'}; $host = inet_aton($host) or return -1; $host = inet_ntoa($host); if (exists $NetConfig{'local_netmask'}) { my $quad = unpack("N", pack("C*", split(/\./, $host))); my $list = $NetConfig{'local_netmask'}; $list = [$list] unless ref($list); foreach (@$list) { my ($net, $bits) = (m#^(\d+\.\d+\.\d+\.\d+)/(\d+)$#) or next; my $mask = ~0 << (32 - $bits); my $addr = unpack("N", pack("C*", split(/\./, $net))); return 0 if (($addr & $mask) == ($quad & $mask)); } return 1; } return 0; } *is_external = \&requires_firewall; 1; __END__ # Net::Cmd.pm # # Copyright (C) 1995-2006 Graham Barr. All rights reserved. # Copyright (C) 2013-2016 Steve Hay. All rights reserved. # This module is free software; you can redistribute it and/or modify it under # the same terms as Perl itself, i.e. under the terms of either the GNU General # Public License or the Artistic License, as specified in the F file. package Net::Cmd; use 5.008001; use strict; use warnings; use Carp; use Exporter; use Symbol 'gensym'; use Errno 'EINTR'; BEGIN { if ($^O eq 'os390') { require Convert::EBCDIC; # Convert::EBCDIC->import; } } our $VERSION = "3.11"; our @ISA = qw(Exporter); our @EXPORT = qw(CMD_INFO CMD_OK CMD_MORE CMD_REJECT CMD_ERROR CMD_PENDING); use constant CMD_INFO => 1; use constant CMD_OK => 2; use constant CMD_MORE => 3; use constant CMD_REJECT => 4; use constant CMD_ERROR => 5; use constant CMD_PENDING => 0; use constant DEF_REPLY_CODE => 421; my %debug = (); my $tr = $^O eq 'os390' ? Convert::EBCDIC->new() : undef; sub toebcdic { my $cmd = shift; unless (exists ${*$cmd}{'net_cmd_asciipeer'}) { my $string = $_[0]; my $ebcdicstr = $tr->toebcdic($string); ${*$cmd}{'net_cmd_asciipeer'} = $string !~ /^\d+/ && $ebcdicstr =~ /^\d+/; } ${*$cmd}{'net_cmd_asciipeer'} ? $tr->toebcdic($_[0]) : $_[0]; } sub toascii { my $cmd = shift; ${*$cmd}{'net_cmd_asciipeer'} ? $tr->toascii($_[0]) : $_[0]; } sub _print_isa { no strict 'refs'; ## no critic (TestingAndDebugging::ProhibitNoStrict) my $pkg = shift; my $cmd = $pkg; $debug{$pkg} ||= 0; my %done = (); my @do = ($pkg); my %spc = ($pkg, ""); while ($pkg = shift @do) { next if defined $done{$pkg}; $done{$pkg} = 1; my $v = defined ${"${pkg}::VERSION"} ? "(" . ${"${pkg}::VERSION"} . ")" : ""; my $spc = $spc{$pkg}; $cmd->debug_print(1, "${spc}${pkg}${v}\n"); if (@{"${pkg}::ISA"}) { @spc{@{"${pkg}::ISA"}} = (" " . $spc{$pkg}) x @{"${pkg}::ISA"}; unshift(@do, @{"${pkg}::ISA"}); } } } sub debug { @_ == 1 or @_ == 2 or croak 'usage: $obj->debug([LEVEL])'; my ($cmd, $level) = @_; my $pkg = ref($cmd) || $cmd; my $oldval = 0; if (ref($cmd)) { $oldval = ${*$cmd}{'net_cmd_debug'} || 0; } else { $oldval = $debug{$pkg} || 0; } return $oldval unless @_ == 2; $level = $debug{$pkg} || 0 unless defined $level; _print_isa($pkg) if ($level && !exists $debug{$pkg}); if (ref($cmd)) { ${*$cmd}{'net_cmd_debug'} = $level; } else { $debug{$pkg} = $level; } $oldval; } sub message { @_ == 1 or croak 'usage: $obj->message()'; my $cmd = shift; wantarray ? @{${*$cmd}{'net_cmd_resp'}} : join("", @{${*$cmd}{'net_cmd_resp'}}); } sub debug_text { $_[2] } sub debug_print { my ($cmd, $out, $text) = @_; print STDERR $cmd, ($out ? '>>> ' : '<<< '), $cmd->debug_text($out, $text); } sub code { @_ == 1 or croak 'usage: $obj->code()'; my $cmd = shift; ${*$cmd}{'net_cmd_code'} = $cmd->DEF_REPLY_CODE unless exists ${*$cmd}{'net_cmd_code'}; ${*$cmd}{'net_cmd_code'}; } sub status { @_ == 1 or croak 'usage: $obj->status()'; my $cmd = shift; substr(${*$cmd}{'net_cmd_code'}, 0, 1); } sub set_status { @_ == 3 or croak 'usage: $obj->set_status(CODE, MESSAGE)'; my $cmd = shift; my ($code, $resp) = @_; $resp = defined $resp ? [$resp] : [] unless ref($resp); (${*$cmd}{'net_cmd_code'}, ${*$cmd}{'net_cmd_resp'}) = ($code, $resp); 1; } sub _syswrite_with_timeout { my $cmd = shift; my $line = shift; my $len = length($line); my $offset = 0; my $win = ""; vec($win, fileno($cmd), 1) = 1; my $timeout = $cmd->timeout || undef; my $initial = time; my $pending = $timeout; local $SIG{PIPE} = 'IGNORE' unless $^O eq 'MacOS'; while ($len) { my $wout; my $nfound = select(undef, $wout = $win, undef, $pending); if ((defined $nfound and $nfound > 0) or -f $cmd) # -f for testing on win32 { my $w = syswrite($cmd, $line, $len, $offset); if (! defined($w) ) { my $err = $!; $cmd->close; $cmd->_set_status_closed($err); return; } $len -= $w; $offset += $w; } elsif ($nfound == -1) { if ( $! == EINTR ) { if ( defined($timeout) ) { redo if ($pending = $timeout - ( time - $initial ) ) > 0; $cmd->_set_status_timeout; return; } redo; } my $err = $!; $cmd->close; $cmd->_set_status_closed($err); return; } else { $cmd->_set_status_timeout; return; } } return 1; } sub _set_status_timeout { my $cmd = shift; my $pkg = ref($cmd) || $cmd; $cmd->set_status($cmd->DEF_REPLY_CODE, "[$pkg] Timeout"); carp(ref($cmd) . ": " . (caller(1))[3] . "(): timeout") if $cmd->debug; } sub _set_status_closed { my $cmd = shift; my $err = shift; my $pkg = ref($cmd) || $cmd; $cmd->set_status($cmd->DEF_REPLY_CODE, "[$pkg] Connection closed"); carp(ref($cmd) . ": " . (caller(1))[3] . "(): unexpected EOF on command channel: $err") if $cmd->debug; } sub _is_closed { my $cmd = shift; if (!defined fileno($cmd)) { $cmd->_set_status_closed($!); return 1; } return 0; } sub command { my $cmd = shift; return $cmd if $cmd->_is_closed; $cmd->dataend() if (exists ${*$cmd}{'net_cmd_last_ch'}); if (scalar(@_)) { my $str = join( " ", map { /\n/ ? do { my $n = $_; $n =~ tr/\n/ /; $n } : $_; } @_ ); $str = $cmd->toascii($str) if $tr; $str .= "\015\012"; $cmd->debug_print(1, $str) if ($cmd->debug); # though documented to return undef on failure, the legacy behavior # was to return $cmd even on failure, so this odd construct does that $cmd->_syswrite_with_timeout($str) or return $cmd; } $cmd; } sub ok { @_ == 1 or croak 'usage: $obj->ok()'; my $code = $_[0]->code; 0 < $code && $code < 400; } sub unsupported { my $cmd = shift; $cmd->set_status(580, 'Unsupported command'); 0; } sub getline { my $cmd = shift; ${*$cmd}{'net_cmd_lines'} ||= []; return shift @{${*$cmd}{'net_cmd_lines'}} if scalar(@{${*$cmd}{'net_cmd_lines'}}); my $partial = defined(${*$cmd}{'net_cmd_partial'}) ? ${*$cmd}{'net_cmd_partial'} : ""; return if $cmd->_is_closed; my $fd = fileno($cmd); my $rin = ""; vec($rin, $fd, 1) = 1; my $buf; until (scalar(@{${*$cmd}{'net_cmd_lines'}})) { my $timeout = $cmd->timeout || undef; my $rout; my $select_ret = select($rout = $rin, undef, undef, $timeout); if ($select_ret > 0) { unless (sysread($cmd, $buf = "", 1024)) { my $err = $!; $cmd->close; $cmd->_set_status_closed($err); return; } substr($buf, 0, 0) = $partial; ## prepend from last sysread my @buf = split(/\015?\012/, $buf, -1); ## break into lines $partial = pop @buf; push(@{${*$cmd}{'net_cmd_lines'}}, map {"$_\n"} @buf); } else { $cmd->_set_status_timeout; return; } } ${*$cmd}{'net_cmd_partial'} = $partial; if ($tr) { foreach my $ln (@{${*$cmd}{'net_cmd_lines'}}) { $ln = $cmd->toebcdic($ln); } } shift @{${*$cmd}{'net_cmd_lines'}}; } sub ungetline { my ($cmd, $str) = @_; ${*$cmd}{'net_cmd_lines'} ||= []; unshift(@{${*$cmd}{'net_cmd_lines'}}, $str); } sub parse_response { return () unless $_[1] =~ s/^(\d\d\d)(.?)//o; ($1, $2 eq "-"); } sub response { my $cmd = shift; my ($code, $more) = (undef) x 2; $cmd->set_status($cmd->DEF_REPLY_CODE, undef); # initialize the response while (1) { my $str = $cmd->getline(); return CMD_ERROR unless defined($str); $cmd->debug_print(0, $str) if ($cmd->debug); ($code, $more) = $cmd->parse_response($str); unless (defined $code) { carp("$cmd: response(): parse error in '$str'") if ($cmd->debug); $cmd->ungetline($str); $@ = $str; # $@ used as tunneling hack return CMD_ERROR; } ${*$cmd}{'net_cmd_code'} = $code; push(@{${*$cmd}{'net_cmd_resp'}}, $str); last unless ($more); } return unless defined $code; substr($code, 0, 1); } sub read_until_dot { my $cmd = shift; my $fh = shift; my $arr = []; while (1) { my $str = $cmd->getline() or return; $cmd->debug_print(0, $str) if ($cmd->debug & 4); last if ($str =~ /^\.\r?\n/o); $str =~ s/^\.\././o; if (defined $fh) { print $fh $str; } else { push(@$arr, $str); } } $arr; } sub datasend { my $cmd = shift; my $arr = @_ == 1 && ref($_[0]) ? $_[0] : \@_; my $line = join("", @$arr); # Perls < 5.10.1 (with the exception of 5.8.9) have a performance problem with # the substitutions below when dealing with strings stored internally in # UTF-8, so downgrade them (if possible). # Data passed to datasend() should be encoded to octets upstream already so # shouldn't even have the UTF-8 flag on to start with, but if it so happens # that the octets are stored in an upgraded string (as can sometimes occur) # then they would still downgrade without fail anyway. # Only Unicode codepoints > 0xFF stored in an upgraded string will fail to # downgrade. We fail silently in that case, and a "Wide character in print" # warning will be emitted later by syswrite(). utf8::downgrade($line, 1) if $] < 5.010001 && $] != 5.008009; return 0 if $cmd->_is_closed; my $last_ch = ${*$cmd}{'net_cmd_last_ch'}; # We have not send anything yet, so last_ch = "\012" means we are at the start of a line $last_ch = ${*$cmd}{'net_cmd_last_ch'} = "\012" unless defined $last_ch; return 1 unless length $line; if ($cmd->debug) { foreach my $b (split(/\n/, $line)) { $cmd->debug_print(1, "$b\n"); } } $line =~ tr/\r\n/\015\012/ unless "\r" eq "\015"; my $first_ch = ''; if ($last_ch eq "\015") { # Remove \012 so it does not get prefixed with another \015 below # and escape the . if there is one following it because the fixup # below will not find it $first_ch = "\012" if $line =~ s/^\012(\.?)/$1$1/; } elsif ($last_ch eq "\012") { # Fixup below will not find the . as the first character of the buffer $first_ch = "." if $line =~ /^\./; } $line =~ s/\015?\012(\.?)/\015\012$1$1/sg; substr($line, 0, 0) = $first_ch; ${*$cmd}{'net_cmd_last_ch'} = substr($line, -1, 1); $cmd->_syswrite_with_timeout($line) or return; 1; } sub rawdatasend { my $cmd = shift; my $arr = @_ == 1 && ref($_[0]) ? $_[0] : \@_; my $line = join("", @$arr); return 0 if $cmd->_is_closed; return 1 unless length($line); if ($cmd->debug) { my $b = "$cmd>>> "; print STDERR $b, join("\n$b", split(/\n/, $line)), "\n"; } $cmd->_syswrite_with_timeout($line) or return; 1; } sub dataend { my $cmd = shift; return 0 if $cmd->_is_closed; my $ch = ${*$cmd}{'net_cmd_last_ch'}; my $tosend; if (!defined $ch) { return 1; } elsif ($ch ne "\012") { $tosend = "\015\012"; } $tosend .= ".\015\012"; $cmd->debug_print(1, ".\n") if ($cmd->debug); $cmd->_syswrite_with_timeout($tosend) or return 0; delete ${*$cmd}{'net_cmd_last_ch'}; $cmd->response() == CMD_OK; } # read and write to tied filehandle sub tied_fh { my $cmd = shift; ${*$cmd}{'net_cmd_readbuf'} = ''; my $fh = gensym(); tie *$fh, ref($cmd), $cmd; return $fh; } # tie to myself sub TIEHANDLE { my $class = shift; my $cmd = shift; return $cmd; } # Tied filehandle read. Reads requested data length, returning # end-of-file when the dot is encountered. sub READ { my $cmd = shift; my ($len, $offset) = @_[1, 2]; return unless exists ${*$cmd}{'net_cmd_readbuf'}; my $done = 0; while (!$done and length(${*$cmd}{'net_cmd_readbuf'}) < $len) { ${*$cmd}{'net_cmd_readbuf'} .= $cmd->getline() or return; $done++ if ${*$cmd}{'net_cmd_readbuf'} =~ s/^\.\r?\n\Z//m; } $_[0] = ''; substr($_[0], $offset + 0) = substr(${*$cmd}{'net_cmd_readbuf'}, 0, $len); substr(${*$cmd}{'net_cmd_readbuf'}, 0, $len) = ''; delete ${*$cmd}{'net_cmd_readbuf'} if $done; return length $_[0]; } sub READLINE { my $cmd = shift; # in this context, we use the presence of readbuf to # indicate that we have not yet reached the eof return unless exists ${*$cmd}{'net_cmd_readbuf'}; my $line = $cmd->getline; return if $line =~ /^\.\r?\n/; $line; } sub PRINT { my $cmd = shift; my ($buf, $len, $offset) = @_; $len ||= length($buf); $offset += 0; return unless $cmd->datasend(substr($buf, $offset, $len)); ${*$cmd}{'net_cmd_sending'}++; # flag that we should call dataend() return $len; } sub CLOSE { my $cmd = shift; my $r = exists(${*$cmd}{'net_cmd_sending'}) ? $cmd->dataend : 1; delete ${*$cmd}{'net_cmd_readbuf'}; delete ${*$cmd}{'net_cmd_sending'}; $r; } 1; __END__ # Net::POP3.pm # # Copyright (C) 1995-2004 Graham Barr. All rights reserved. # Copyright (C) 2013-2016 Steve Hay. All rights reserved. # This module is free software; you can redistribute it and/or modify it under # the same terms as Perl itself, i.e. under the terms of either the GNU General # Public License or the Artistic License, as specified in the F file. package Net::POP3; use 5.008001; use strict; use warnings; use Carp; use IO::Socket; use Net::Cmd; use Net::Config; our $VERSION = "3.11"; # Code for detecting if we can use SSL my $ssl_class = eval { require IO::Socket::SSL; # first version with default CA on most platforms no warnings 'numeric'; IO::Socket::SSL->VERSION(2.007); } && 'IO::Socket::SSL'; my $nossl_warn = !$ssl_class && 'To use SSL please install IO::Socket::SSL with version>=2.007'; # Code for detecting if we can use IPv6 my $family_key = 'Domain'; my $inet6_class = eval { require IO::Socket::IP; no warnings 'numeric'; IO::Socket::IP->VERSION(0.25) || die; $family_key = 'Family'; } && 'IO::Socket::IP' || eval { require IO::Socket::INET6; no warnings 'numeric'; IO::Socket::INET6->VERSION(2.62); } && 'IO::Socket::INET6'; sub can_ssl { $ssl_class }; sub can_inet6 { $inet6_class }; our @ISA = ('Net::Cmd', $inet6_class || 'IO::Socket::INET'); sub new { my $self = shift; my $type = ref($self) || $self; my ($host, %arg); if (@_ % 2) { $host = shift; %arg = @_; } else { %arg = @_; $host = delete $arg{Host}; } my $hosts = defined $host ? [$host] : $NetConfig{pop3_hosts}; my $obj; if ($arg{SSL}) { # SSL from start die $nossl_warn if !$ssl_class; $arg{Port} ||= 995; } $arg{Timeout} = 120 if ! defined $arg{Timeout}; foreach my $h (@{$hosts}) { $obj = $type->SUPER::new( PeerAddr => ($host = $h), PeerPort => $arg{Port} || 'pop3(110)', Proto => 'tcp', $family_key => $arg{Domain} || $arg{Family}, LocalAddr => $arg{LocalAddr}, LocalPort => exists($arg{ResvPort}) ? $arg{ResvPort} : $arg{LocalPort}, Timeout => $arg{Timeout}, ) and last; } return unless defined $obj; ${*$obj}{'net_pop3_arg'} = \%arg; ${*$obj}{'net_pop3_host'} = $host; if ($arg{SSL}) { Net::POP3::_SSL->start_SSL($obj,%arg) or return; } $obj->autoflush(1); $obj->debug(exists $arg{Debug} ? $arg{Debug} : undef); unless ($obj->response() == CMD_OK) { $obj->close(); return; } ${*$obj}{'net_pop3_banner'} = $obj->message; $obj; } sub host { my $me = shift; ${*$me}{'net_pop3_host'}; } ## ## We don't want people sending me their passwords when they report problems ## now do we :-) ## sub debug_text { $_[2] =~ /^(pass|rpop)/i ? "$1 ....\n" : $_[2]; } sub login { @_ >= 1 && @_ <= 3 or croak 'usage: $pop3->login( USER, PASS )'; my ($me, $user, $pass) = @_; if (@_ <= 2) { ($user, $pass) = $me->_lookup_credentials($user); } $me->user($user) and $me->pass($pass); } sub starttls { my $self = shift; $ssl_class or die $nossl_warn; $self->_STLS or return; Net::POP3::_SSL->start_SSL($self, %{ ${*$self}{'net_pop3_arg'} }, # (ssl) args given in new @_ # more (ssl) args ) or return; return 1; } sub apop { @_ >= 1 && @_ <= 3 or croak 'usage: $pop3->apop( USER, PASS )'; my ($me, $user, $pass) = @_; my $banner; my $md; if (eval { local $SIG{__DIE__}; require Digest::MD5 }) { $md = Digest::MD5->new(); } elsif (eval { local $SIG{__DIE__}; require MD5 }) { $md = MD5->new(); } else { carp "You need to install Digest::MD5 or MD5 to use the APOP command"; return; } return unless ($banner = (${*$me}{'net_pop3_banner'} =~ /(<.*>)/)[0]); if (@_ <= 2) { ($user, $pass) = $me->_lookup_credentials($user); } $md->add($banner, $pass); return unless ($me->_APOP($user, $md->hexdigest)); $me->_get_mailbox_count(); } sub user { @_ == 2 or croak 'usage: $pop3->user( USER )'; $_[0]->_USER($_[1]) ? 1 : undef; } sub pass { @_ == 2 or croak 'usage: $pop3->pass( PASS )'; my ($me, $pass) = @_; return unless ($me->_PASS($pass)); $me->_get_mailbox_count(); } sub reset { @_ == 1 or croak 'usage: $obj->reset()'; my $me = shift; return 0 unless ($me->_RSET); if (defined ${*$me}{'net_pop3_mail'}) { local $_; foreach (@{${*$me}{'net_pop3_mail'}}) { delete $_->{'net_pop3_deleted'}; } } } sub last { @_ == 1 or croak 'usage: $obj->last()'; return unless $_[0]->_LAST && $_[0]->message =~ /(\d+)/; return $1; } sub top { @_ == 2 || @_ == 3 or croak 'usage: $pop3->top( MSGNUM [, NUMLINES ])'; my $me = shift; return unless $me->_TOP($_[0], $_[1] || 0); $me->read_until_dot; } sub popstat { @_ == 1 or croak 'usage: $pop3->popstat()'; my $me = shift; return () unless $me->_STAT && $me->message =~ /(\d+)\D+(\d+)/; ($1 || 0, $2 || 0); } sub list { @_ == 1 || @_ == 2 or croak 'usage: $pop3->list( [ MSGNUM ] )'; my $me = shift; return unless $me->_LIST(@_); if (@_) { $me->message =~ /\d+\D+(\d+)/; return $1 || undef; } my $info = $me->read_until_dot or return; my %hash = map { (/(\d+)\D+(\d+)/) } @$info; return \%hash; } sub get { @_ == 2 or @_ == 3 or croak 'usage: $pop3->get( MSGNUM [, FH ])'; my $me = shift; return unless $me->_RETR(shift); $me->read_until_dot(@_); } sub getfh { @_ == 2 or croak 'usage: $pop3->getfh( MSGNUM )'; my $me = shift; return unless $me->_RETR(shift); return $me->tied_fh; } sub delete { @_ == 2 or croak 'usage: $pop3->delete( MSGNUM )'; my $me = shift; return 0 unless $me->_DELE(@_); ${*$me}{'net_pop3_deleted'} = 1; } sub uidl { @_ == 1 || @_ == 2 or croak 'usage: $pop3->uidl( [ MSGNUM ] )'; my $me = shift; my $uidl; $me->_UIDL(@_) or return; if (@_) { $uidl = ($me->message =~ /\d+\s+([\041-\176]+)/)[0]; } else { my $ref = $me->read_until_dot or return; $uidl = {}; foreach my $ln (@$ref) { my ($msg, $uid) = $ln =~ /^\s*(\d+)\s+([\041-\176]+)/; $uidl->{$msg} = $uid; } } return $uidl; } sub ping { @_ == 2 or croak 'usage: $pop3->ping( USER )'; my $me = shift; return () unless $me->_PING(@_) && $me->message =~ /(\d+)\D+(\d+)/; ($1 || 0, $2 || 0); } sub _lookup_credentials { my ($me, $user) = @_; require Net::Netrc; $user ||= eval { local $SIG{__DIE__}; (getpwuid($>))[0] } || $ENV{NAME} || $ENV{USER} || $ENV{LOGNAME}; my $m = Net::Netrc->lookup(${*$me}{'net_pop3_host'}, $user); $m ||= Net::Netrc->lookup(${*$me}{'net_pop3_host'}); my $pass = $m ? $m->password || "" : ""; ($user, $pass); } sub _get_mailbox_count { my ($me) = @_; my $ret = ${*$me}{'net_pop3_count'} = ($me->message =~ /(\d+)\s+message/io) ? $1 : ($me->popstat)[0]; $ret ? $ret : "0E0"; } sub _STAT { shift->command('STAT' )->response() == CMD_OK } sub _LIST { shift->command('LIST', @_)->response() == CMD_OK } sub _RETR { shift->command('RETR', $_[0])->response() == CMD_OK } sub _DELE { shift->command('DELE', $_[0])->response() == CMD_OK } sub _NOOP { shift->command('NOOP' )->response() == CMD_OK } sub _RSET { shift->command('RSET' )->response() == CMD_OK } sub _QUIT { shift->command('QUIT' )->response() == CMD_OK } sub _TOP { shift->command( 'TOP', @_)->response() == CMD_OK } sub _UIDL { shift->command('UIDL', @_)->response() == CMD_OK } sub _USER { shift->command('USER', $_[0])->response() == CMD_OK } sub _PASS { shift->command('PASS', $_[0])->response() == CMD_OK } sub _APOP { shift->command('APOP', @_)->response() == CMD_OK } sub _PING { shift->command('PING', $_[0])->response() == CMD_OK } sub _RPOP { shift->command('RPOP', $_[0])->response() == CMD_OK } sub _LAST { shift->command('LAST' )->response() == CMD_OK } sub _CAPA { shift->command('CAPA' )->response() == CMD_OK } sub _STLS { shift->command("STLS", )->response() == CMD_OK } sub quit { my $me = shift; $me->_QUIT; $me->close; } sub DESTROY { my $me = shift; if (defined fileno($me) and ${*$me}{'net_pop3_deleted'}) { $me->reset; $me->quit; } } ## ## POP3 has weird responses, so we emulate them to look the same :-) ## sub response { my $cmd = shift; my $str = $cmd->getline() or return; my $code = "500"; $cmd->debug_print(0, $str) if ($cmd->debug); if ($str =~ s/^\+OK\s*//io) { $code = "200"; } elsif ($str =~ s/^\+\s*//io) { $code = "300"; } else { $str =~ s/^-ERR\s*//io; } ${*$cmd}{'net_cmd_resp'} = [$str]; ${*$cmd}{'net_cmd_code'} = $code; substr($code, 0, 1); } sub capa { my $this = shift; my ($capa, %capabilities); # Fake a capability here $capabilities{APOP} = '' if ($this->banner() =~ /<.*>/); if ($this->_CAPA()) { $capabilities{CAPA} = 1; $capa = $this->read_until_dot(); %capabilities = (%capabilities, map {/^\s*(\S+)\s*(.*)/} @$capa); } else { # Check AUTH for SASL capabilities if ($this->command('AUTH')->response() == CMD_OK) { my $mechanism = $this->read_until_dot(); $capabilities{SASL} = join " ", map {m/([A-Z0-9_-]+)/} @{$mechanism}; } } return ${*$this}{'net_pop3e_capabilities'} = \%capabilities; } sub capabilities { my $this = shift; ${*$this}{'net_pop3e_capabilities'} || $this->capa; } sub auth { my ($self, $username, $password) = @_; eval { require MIME::Base64; require Authen::SASL; } or $self->set_status(500, ["Need MIME::Base64 and Authen::SASL todo auth"]), return 0; my $capa = $self->capa; my $mechanisms = $capa->{SASL} || 'CRAM-MD5'; my $sasl; if (ref($username) and UNIVERSAL::isa($username, 'Authen::SASL')) { $sasl = $username; my $user_mech = $sasl->mechanism || ''; my @user_mech = split(/\s+/, $user_mech); my %user_mech; @user_mech{@user_mech} = (); my @server_mech = split(/\s+/, $mechanisms); my @mech = @user_mech ? grep { exists $user_mech{$_} } @server_mech : @server_mech; unless (@mech) { $self->set_status( 500, [ 'Client SASL mechanisms (', join(', ', @user_mech), ') do not match the SASL mechnism the server announces (', join(', ', @server_mech), ')', ] ); return 0; } $sasl->mechanism(join(" ", @mech)); } else { die "auth(username, password)" if not length $username; $sasl = Authen::SASL->new( mechanism => $mechanisms, callback => { user => $username, pass => $password, authname => $username, } ); } # We should probably allow the user to pass the host, but I don't # currently know and SASL mechanisms that are used by smtp that need it my ($hostname) = split /:/, ${*$self}{'net_pop3_host'}; my $client = eval { $sasl->client_new('pop', $hostname, 0) }; unless ($client) { my $mech = $sasl->mechanism; $self->set_status( 500, [ " Authen::SASL failure: $@", '(please check if your local Authen::SASL installation', "supports mechanism '$mech'" ] ); return 0; } my ($token) = $client->client_start or do { my $mech = $client->mechanism; $self->set_status( 500, [ ' Authen::SASL failure: $client->client_start ', "mechanism '$mech' hostname #$hostname#", $client->error ] ); return 0; }; # We don't support sasl mechanisms that encrypt the socket traffic. # todo that we would really need to change the ISA hierarchy # so we don't inherit from IO::Socket, but instead hold it in an attribute my @cmd = ("AUTH", $client->mechanism); my $code; push @cmd, MIME::Base64::encode_base64($token, '') if defined $token and length $token; while (($code = $self->command(@cmd)->response()) == CMD_MORE) { my ($token) = $client->client_step(MIME::Base64::decode_base64(($self->message)[0])) or do { $self->set_status( 500, [ ' Authen::SASL failure: $client->client_step ', "mechanism '", $client->mechanism, " hostname #$hostname#, ", $client->error ] ); return 0; }; @cmd = (MIME::Base64::encode_base64(defined $token ? $token : '', '')); } $code == CMD_OK; } sub banner { my $this = shift; return ${*$this}{'net_pop3_banner'}; } { package Net::POP3::_SSL; our @ISA = ( $ssl_class ? ($ssl_class):(), 'Net::POP3' ); sub starttls { die "POP3 connection is already in SSL mode" } sub start_SSL { my ($class,$pop3,%arg) = @_; delete @arg{ grep { !m{^SSL_} } keys %arg }; ( $arg{SSL_verifycn_name} ||= $pop3->host ) =~s{(?can_client_sni; $arg{SSL_verifycn_scheme} ||= 'pop3'; my $ok = $class->SUPER::start_SSL($pop3,%arg); $@ = $ssl_class->errstr if !$ok; return $ok; } } 1; __END__ package Net::netent; use strict; use 5.006_001; our $VERSION = '1.01'; our(@EXPORT, @EXPORT_OK, %EXPORT_TAGS); our ( $n_name, @n_aliases, $n_addrtype, $n_net ); BEGIN { use Exporter (); @EXPORT = qw(getnetbyname getnetbyaddr getnet); @EXPORT_OK = qw( $n_name @n_aliases $n_addrtype $n_net ); %EXPORT_TAGS = ( FIELDS => [ @EXPORT_OK, @EXPORT ] ); } # Class::Struct forbids use of @ISA sub import { goto &Exporter::import } use Class::Struct qw(struct); struct 'Net::netent' => [ name => '$', aliases => '@', addrtype => '$', net => '$', ]; sub populate (@) { return unless @_; my $nob = new(); $n_name = $nob->[0] = $_[0]; @n_aliases = @{ $nob->[1] } = split ' ', $_[1]; $n_addrtype = $nob->[2] = $_[2]; $n_net = $nob->[3] = $_[3]; return $nob; } sub getnetbyname ($) { populate(CORE::getnetbyname(shift)) } sub getnetbyaddr ($;$) { my ($net, $addrtype); $net = shift; require Socket if @_; $addrtype = @_ ? shift : Socket::AF_INET(); populate(CORE::getnetbyaddr($net, $addrtype)) } sub getnet($) { if ($_[0] =~ /^\d+(?:\.\d+(?:\.\d+(?:\.\d+)?)?)?$/) { require Socket; &getnetbyaddr(Socket::inet_aton(shift)); } else { &getnetbyname; } } 1; __END__ ## ## Generic data connection package ## package Net::FTP::dataconn; use 5.008001; use strict; use warnings; use Carp; use Errno; use Net::Cmd; our $VERSION = '3.11'; $Net::FTP::IOCLASS or die "please load Net::FTP before Net::FTP::dataconn"; our @ISA = $Net::FTP::IOCLASS; sub reading { my $data = shift; ${*$data}{'net_ftp_bytesread'} = 0; } sub abort { my $data = shift; my $ftp = ${*$data}{'net_ftp_cmd'}; # no need to abort if we have finished the xfer return $data->close if ${*$data}{'net_ftp_eof'}; # for some reason if we continuously open RETR connections and not # read a single byte, then abort them after a while the server will # close our connection, this prevents the unexpected EOF on the # command channel -- GMB if (exists ${*$data}{'net_ftp_bytesread'} && (${*$data}{'net_ftp_bytesread'} == 0)) { my $buf = ""; my $timeout = $data->timeout; $data->can_read($timeout) && sysread($data, $buf, 1); } ${*$data}{'net_ftp_eof'} = 1; # fake $ftp->abort; # this will close me } sub _close { my $data = shift; my $ftp = ${*$data}{'net_ftp_cmd'}; $data->SUPER::close(); delete ${*$ftp}{'net_ftp_dataconn'} if defined $ftp && exists ${*$ftp}{'net_ftp_dataconn'} && $data == ${*$ftp}{'net_ftp_dataconn'}; } sub close { my $data = shift; my $ftp = ${*$data}{'net_ftp_cmd'}; if (exists ${*$data}{'net_ftp_bytesread'} && !${*$data}{'net_ftp_eof'}) { my $junk; eval { local($SIG{__DIE__}); $data->read($junk, 1, 0) }; return $data->abort unless ${*$data}{'net_ftp_eof'}; } $data->_close; return unless defined $ftp; $ftp->response() == CMD_OK && $ftp->message =~ /unique file name:\s*(\S*)\s*\)/ && (${*$ftp}{'net_ftp_unique'} = $1); $ftp->status == CMD_OK; } sub _select { my ($data, $timeout, $do_read) = @_; my ($rin, $rout, $win, $wout, $tout, $nfound); vec($rin = '', fileno($data), 1) = 1; ($win, $rin) = ($rin, $win) unless $do_read; while (1) { $nfound = select($rout = $rin, $wout = $win, undef, $tout = $timeout); last if $nfound >= 0; croak "select: $!" unless $!{EINTR}; } $nfound; } sub can_read { _select(@_[0, 1], 1); } sub can_write { _select(@_[0, 1], 0); } sub cmd { my $ftp = shift; ${*$ftp}{'net_ftp_cmd'}; } sub bytes_read { my $ftp = shift; ${*$ftp}{'net_ftp_bytesread'} || 0; } 1; __END__ ## ## Package to read/write on BINARY data connections ## package Net::FTP::I; use 5.008001; use strict; use warnings; use Carp; use Net::FTP::dataconn; our @ISA = qw(Net::FTP::dataconn); our $VERSION = "3.11"; our $buf; sub read { my $data = shift; local *buf = \$_[0]; shift; my $size = shift || croak 'read($buf,$size,[$timeout])'; my $timeout = @_ ? shift: $data->timeout; my $n; if ($size > length ${*$data} and !${*$data}{'net_ftp_eof'}) { $data->can_read($timeout) or croak "Timeout"; my $blksize = ${*$data}{'net_ftp_blksize'}; $blksize = $size if $size > $blksize; unless ($n = sysread($data, ${*$data}, $blksize, length ${*$data})) { return unless defined $n; ${*$data}{'net_ftp_eof'} = 1; } } $buf = substr(${*$data}, 0, $size); $n = length($buf); substr(${*$data}, 0, $n) = ''; ${*$data}{'net_ftp_bytesread'} += $n; $n; } sub write { my $data = shift; local *buf = \$_[0]; shift; my $size = shift || croak 'write($buf,$size,[$timeout])'; my $timeout = @_ ? shift: $data->timeout; # If the remote server has closed the connection we will be signal'd # when we write. This can happen if the disk on the remote server fills up local $SIG{PIPE} = 'IGNORE' unless ($SIG{PIPE} || '') eq 'IGNORE' or $^O eq 'MacOS'; my $sent = $size; my $off = 0; my $blksize = ${*$data}{'net_ftp_blksize'}; while ($sent > 0) { $data->can_write($timeout) or croak "Timeout"; my $n = syswrite($data, $buf, $sent > $blksize ? $blksize : $sent, $off); return unless defined($n); $sent -= $n; $off += $n; } $size; } 1; ## ## Package to read/write on ASCII data connections ## package Net::FTP::A; use 5.008001; use strict; use warnings; use Carp; use Net::FTP::dataconn; our @ISA = qw(Net::FTP::dataconn); our $VERSION = "3.11"; our $buf; sub read { my $data = shift; local *buf = \$_[0]; shift; my $size = shift || croak 'read($buf,$size,[$offset])'; my $timeout = @_ ? shift: $data->timeout; if (length(${*$data}) < $size && !${*$data}{'net_ftp_eof'}) { my $blksize = ${*$data}{'net_ftp_blksize'}; $blksize = $size if $size > $blksize; my $l = 0; my $n; READ: { my $readbuf = defined(${*$data}{'net_ftp_cr'}) ? "\015" : ''; $data->can_read($timeout) or croak "Timeout"; if ($n = sysread($data, $readbuf, $blksize, length $readbuf)) { ${*$data}{'net_ftp_bytesread'} += $n; ${*$data}{'net_ftp_cr'} = substr($readbuf, -1) eq "\015" ? chop($readbuf) : undef; } else { return unless defined $n; ${*$data}{'net_ftp_eof'} = 1; } $readbuf =~ s/\015\012/\n/sgo; ${*$data} .= $readbuf; unless (length(${*$data})) { redo READ if ($n > 0); $size = length(${*$data}) if ($n == 0); } } } $buf = substr(${*$data}, 0, $size); substr(${*$data}, 0, $size) = ''; length $buf; } sub write { my $data = shift; local *buf = \$_[0]; shift; my $size = shift || croak 'write($buf,$size,[$timeout])'; my $timeout = @_ ? shift: $data->timeout; my $nr = (my $tmp = substr($buf, 0, $size)) =~ tr/\r\n/\015\012/; $tmp =~ s/(?can_write($timeout) or croak "Timeout"; $off += $wrote; $wrote = syswrite($data, substr($tmp, $off), $len > $blksize ? $blksize : $len); return unless defined($wrote); $len -= $wrote; } $size; } 1; package Net::FTP::L; use 5.008001; use strict; use warnings; use Net::FTP::I; our @ISA = qw(Net::FTP::I); our $VERSION = "3.11"; 1; package Net::FTP::E; use 5.008001; use strict; use warnings; use Net::FTP::I; our @ISA = qw(Net::FTP::I); our $VERSION = "3.11"; 1; package Time::gmtime; use strict; use 5.006_001; use Time::tm; our (@ISA, @EXPORT, @EXPORT_OK, %EXPORT_TAGS, $VERSION); our ( $tm_sec, $tm_min, $tm_hour, $tm_mday, $tm_mon, $tm_year, $tm_wday, $tm_yday, $tm_isdst, ); BEGIN { use Exporter (); @ISA = qw(Exporter Time::tm); @EXPORT = qw(gmtime gmctime); @EXPORT_OK = qw( $tm_sec $tm_min $tm_hour $tm_mday $tm_mon $tm_year $tm_wday $tm_yday $tm_isdst ); %EXPORT_TAGS = ( FIELDS => [ @EXPORT_OK, @EXPORT ] ); $VERSION = 1.04; } sub populate (@) { return unless @_; my $tmob = Time::tm->new(); @$tmob = ( $tm_sec, $tm_min, $tm_hour, $tm_mday, $tm_mon, $tm_year, $tm_wday, $tm_yday, $tm_isdst ) = @_; return $tmob; } sub gmtime (;$) { populate CORE::gmtime(@_ ? shift : time)} sub gmctime (;$) { scalar CORE::gmtime(@_ ? shift : time)} 1; __END__ package Time::localtime; use strict; use 5.006_001; use Time::tm; our (@ISA, @EXPORT, @EXPORT_OK, %EXPORT_TAGS, $VERSION); our ( $tm_sec, $tm_min, $tm_hour, $tm_mday, $tm_mon, $tm_year, $tm_wday, $tm_yday, $tm_isdst ); BEGIN { use Exporter (); @ISA = qw(Exporter Time::tm); @EXPORT = qw(localtime ctime); @EXPORT_OK = qw( $tm_sec $tm_min $tm_hour $tm_mday $tm_mon $tm_year $tm_wday $tm_yday $tm_isdst ); %EXPORT_TAGS = ( FIELDS => [ @EXPORT_OK, @EXPORT ] ); $VERSION = 1.03; } sub populate (@) { return unless @_; my $tmob = Time::tm->new(); @$tmob = ( $tm_sec, $tm_min, $tm_hour, $tm_mday, $tm_mon, $tm_year, $tm_wday, $tm_yday, $tm_isdst ) = @_; return $tmob; } sub localtime (;$) { populate CORE::localtime(@_ ? shift : time)} sub ctime (;$) { scalar CORE::localtime(@_ ? shift : time) } 1; __END__ package Time::Local; use strict; use Carp (); use Exporter; our $VERSION = '1.25'; use parent 'Exporter'; our @EXPORT = qw( timegm timelocal ); our @EXPORT_OK = qw( timegm_nocheck timelocal_nocheck ); my @MonthDays = ( 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 ); # Determine breakpoint for rolling century my $ThisYear = ( localtime() )[5]; my $Breakpoint = ( $ThisYear + 50 ) % 100; my $NextCentury = $ThisYear - $ThisYear % 100; $NextCentury += 100 if $Breakpoint < 50; my $Century = $NextCentury - 100; my $SecOff = 0; my ( %Options, %Cheat ); use constant SECS_PER_MINUTE => 60; use constant SECS_PER_HOUR => 3600; use constant SECS_PER_DAY => 86400; my $MaxDay; if ( $] < 5.012000 ) { require Config; ## no critic (Variables::ProhibitPackageVars) my $MaxInt; if ( $^O eq 'MacOS' ) { # time_t is unsigned... $MaxInt = ( 1 << ( 8 * $Config::Config{ivsize} ) ) - 1; ## no critic qw(ProhibitPackageVars) } else { $MaxInt = ( ( 1 << ( 8 * $Config::Config{ivsize} - 2 ) ) - 1 ) * 2 + 1; ## no critic qw(ProhibitPackageVars) } $MaxDay = int( ( $MaxInt - ( SECS_PER_DAY / 2 ) ) / SECS_PER_DAY ) - 1; } else { # recent localtime()'s limit is the year 2**31 $MaxDay = 365 * ( 2**31 ); } # Determine the EPOC day for this machine my $Epoc = 0; if ( $^O eq 'vos' ) { # work around posix-977 -- VOS doesn't handle dates in the range # 1970-1980. $Epoc = _daygm( 0, 0, 0, 1, 0, 70, 4, 0 ); } elsif ( $^O eq 'MacOS' ) { $MaxDay *= 2 if $^O eq 'MacOS'; # time_t unsigned ... quick hack? # MacOS time() is seconds since 1 Jan 1904, localtime # so we need to calculate an offset to apply later $Epoc = 693901; $SecOff = timelocal( localtime(0) ) - timelocal( gmtime(0) ); $Epoc += _daygm( gmtime(0) ); } else { $Epoc = _daygm( gmtime(0) ); } %Cheat = (); # clear the cache as epoc has changed sub _daygm { # This is written in such a byzantine way in order to avoid # lexical variables and sub calls, for speed return $_[3] + ( $Cheat{ pack( 'ss', @_[ 4, 5 ] ) } ||= do { my $month = ( $_[4] + 10 ) % 12; my $year = $_[5] + 1900 - int( $month / 10 ); ( ( 365 * $year ) + int( $year / 4 ) - int( $year / 100 ) + int( $year / 400 ) + int( ( ( $month * 306 ) + 5 ) / 10 ) ) - $Epoc; } ); } sub _timegm { my $sec = $SecOff + $_[0] + ( SECS_PER_MINUTE * $_[1] ) + ( SECS_PER_HOUR * $_[2] ); return $sec + ( SECS_PER_DAY * &_daygm ); } sub timegm { my ( $sec, $min, $hour, $mday, $month, $year ) = @_; if ( $year >= 1000 ) { $year -= 1900; } elsif ( $year < 100 and $year >= 0 ) { $year += ( $year > $Breakpoint ) ? $Century : $NextCentury; } unless ( $Options{no_range_check} ) { Carp::croak("Month '$month' out of range 0..11") if $month > 11 or $month < 0; my $md = $MonthDays[$month]; ++$md if $month == 1 && _is_leap_year( $year + 1900 ); Carp::croak("Day '$mday' out of range 1..$md") if $mday > $md or $mday < 1; Carp::croak("Hour '$hour' out of range 0..23") if $hour > 23 or $hour < 0; Carp::croak("Minute '$min' out of range 0..59") if $min > 59 or $min < 0; Carp::croak("Second '$sec' out of range 0..59") if $sec >= 60 or $sec < 0; } my $days = _daygm( undef, undef, undef, $mday, $month, $year ); unless ( $Options{no_range_check} or abs($days) < $MaxDay ) { my $msg = q{}; $msg .= "Day too big - $days > $MaxDay\n" if $days > $MaxDay; $year += 1900; $msg .= "Cannot handle date ($sec, $min, $hour, $mday, $month, $year)"; Carp::croak($msg); } return $sec + $SecOff + ( SECS_PER_MINUTE * $min ) + ( SECS_PER_HOUR * $hour ) + ( SECS_PER_DAY * $days ); } sub _is_leap_year { return 0 if $_[0] % 4; return 1 if $_[0] % 100; return 0 if $_[0] % 400; return 1; } sub timegm_nocheck { local $Options{no_range_check} = 1; return &timegm; } sub timelocal { my $ref_t = &timegm; my $loc_for_ref_t = _timegm( localtime($ref_t) ); my $zone_off = $loc_for_ref_t - $ref_t or return $loc_for_ref_t; # Adjust for timezone my $loc_t = $ref_t - $zone_off; # Are we close to a DST change or are we done my $dst_off = $ref_t - _timegm( localtime($loc_t) ); # If this evaluates to true, it means that the value in $loc_t is # the _second_ hour after a DST change where the local time moves # backward. if ( !$dst_off && ( ( $ref_t - SECS_PER_HOUR ) - _timegm( localtime( $loc_t - SECS_PER_HOUR ) ) < 0 ) ) { return $loc_t - SECS_PER_HOUR; } # Adjust for DST change $loc_t += $dst_off; return $loc_t if $dst_off > 0; # If the original date was a non-extent gap in a forward DST jump, # we should now have the wrong answer - undo the DST adjustment my ( $s, $m, $h ) = localtime($loc_t); $loc_t -= $dst_off if $s != $_[0] || $m != $_[1] || $h != $_[2]; return $loc_t; } sub timelocal_nocheck { local $Options{no_range_check} = 1; return &timelocal; } 1; # ABSTRACT: Efficiently compute time from local and GMT time __END__ package Time::tm; use strict; our $VERSION = '1.00'; use Class::Struct qw(struct); struct('Time::tm' => [ map { $_ => '$' } qw{ sec min hour mday mon year wday yday isdst } ]); 1; __END__ use strict; use warnings; package Perl::OSType; # ABSTRACT: Map Perl operating system names to generic types our $VERSION = '1.010'; require Exporter; our @ISA = qw(Exporter); our %EXPORT_TAGS = ( all => [qw( os_type is_os_type )] ); our @EXPORT_OK = @{ $EXPORT_TAGS{all} }; # originally taken from Module::Build by Ken Williams et al. my %OSTYPES = qw( aix Unix bsdos Unix beos Unix bitrig Unix dgux Unix dragonfly Unix dynixptx Unix freebsd Unix linux Unix haiku Unix hpux Unix iphoneos Unix irix Unix darwin Unix machten Unix midnightbsd Unix minix Unix mirbsd Unix next Unix openbsd Unix netbsd Unix dec_osf Unix nto Unix svr4 Unix svr5 Unix sco Unix sco_sv Unix unicos Unix unicosmk Unix solaris Unix sunos Unix cygwin Unix msys Unix os2 Unix interix Unix gnu Unix gnukfreebsd Unix nto Unix qnx Unix android Unix dos Windows MSWin32 Windows os390 EBCDIC os400 EBCDIC posix-bc EBCDIC vmesa EBCDIC MacOS MacOS VMS VMS vos VOS riscos RiscOS amigaos Amiga mpeix MPEiX ); sub os_type { my ($os) = @_; $os = $^O unless defined $os; return $OSTYPES{$os} || q{}; } sub is_os_type { my ( $type, $os ) = @_; return unless $type; $os = $^O unless defined $os; return os_type($os) eq $type; } 1; __END__ # vim: ts=4 sts=4 sw=4 et: package User::grent; use strict; use 5.006_001; our $VERSION = '1.02'; our(@EXPORT, @EXPORT_OK, %EXPORT_TAGS); our ($gr_name, $gr_gid, $gr_passwd, $gr_mem, @gr_members); BEGIN { use Exporter (); @EXPORT = qw(getgrent getgrgid getgrnam getgr); @EXPORT_OK = qw($gr_name $gr_gid $gr_passwd $gr_mem @gr_members); %EXPORT_TAGS = ( FIELDS => [ @EXPORT_OK, @EXPORT ] ); } # Class::Struct forbids use of @ISA sub import { goto &Exporter::import } use Class::Struct qw(struct); struct 'User::grent' => [ name => '$', passwd => '$', gid => '$', members => '@', ]; sub populate (@) { return unless @_; my $gob = new(); ($gr_name, $gr_passwd, $gr_gid) = @$gob[0,1,2] = @_[0,1,2]; @gr_members = @{$gob->[3]} = split ' ', $_[3]; return $gob; } sub getgrent ( ) { populate(CORE::getgrent()) } sub getgrnam ($) { populate(CORE::getgrnam(shift)) } sub getgrgid ($) { populate(CORE::getgrgid(shift)) } sub getgr ($) { ($_[0] =~ /^\d+/) ? &getgrgid : &getgrnam } 1; __END__ package User::pwent; use 5.006; our $VERSION = '1.01'; use strict; use warnings; use Config; use Carp; our(@EXPORT, @EXPORT_OK, %EXPORT_TAGS); our ( $pw_name, $pw_passwd, $pw_uid, $pw_gid, $pw_gecos, $pw_dir, $pw_shell, $pw_expire, $pw_change, $pw_class, $pw_age, $pw_quota, $pw_comment, ); BEGIN { use Exporter (); @EXPORT = qw(getpwent getpwuid getpwnam getpw); @EXPORT_OK = qw( pw_has $pw_name $pw_passwd $pw_uid $pw_gid $pw_gecos $pw_dir $pw_shell $pw_expire $pw_change $pw_class $pw_age $pw_quota $pw_comment ); %EXPORT_TAGS = ( FIELDS => [ grep(/^\$pw_/, @EXPORT_OK), @EXPORT ], ALL => [ @EXPORT, @EXPORT_OK ], ); } # # XXX: these mean somebody hacked this module's source # without understanding the underlying assumptions. # my $IE = "[INTERNAL ERROR]"; # Class::Struct forbids use of @ISA sub import { goto &Exporter::import } use Class::Struct qw(struct); struct 'User::pwent' => [ name => '$', # pwent[0] passwd => '$', # pwent[1] uid => '$', # pwent[2] gid => '$', # pwent[3] # you'll only have one/none of these three change => '$', # pwent[4] age => '$', # pwent[4] quota => '$', # pwent[4] # you'll only have one/none of these two comment => '$', # pwent[5] class => '$', # pwent[5] # you might not have this one gecos => '$', # pwent[6] dir => '$', # pwent[7] shell => '$', # pwent[8] # you might not have this one expire => '$', # pwent[9] ]; # init our groks hash to be true if the built platform knew how # to do each struct pwd field that perl can ever under any circumstances # know about. we do not use /^pw_?/, but just the tails. sub _feature_init { our %Groks; # whether build system knew how to do this feature for my $feep ( qw{ pwage pwchange pwclass pwcomment pwexpire pwgecos pwpasswd pwquota } ) { my $short = $feep =~ /^pw(.*)/ ? $1 : do { # not cluck, as we know we called ourselves, # and a confession is probably imminent anyway warn("$IE $feep is a funny struct pwd field"); $feep; }; exists $Config{ "d_" . $feep } || confess("$IE Configure doesn't d_$feep"); $Groks{$short} = defined $Config{ "d_" . $feep }; } # assume that any that are left are always there for my $feep (grep /^\$pw_/s, @EXPORT_OK) { $feep =~ /^\$pw_(.*)/; $Groks{$1} = 1 unless defined $Groks{$1}; } } # With arguments, reports whether one or more fields are all implemented # in the build machine's struct pwd pw_*. May be whitespace separated. # We do not use /^pw_?/, just the tails. # # Without arguments, returns the list of fields implemented on build # machine, space separated in scalar context. # # Takes exception to being asked whether this machine's struct pwd has # a field that Perl never knows how to provide under any circumstances. # If the module does this idiocy to itself, the explosion is noisier. # sub pw_has { our %Groks; # whether build system knew how to do this feature my $cando = 1; my $sploder = caller() ne __PACKAGE__ ? \&croak : sub { confess("$IE @_") }; if (@_ == 0) { my @valid = sort grep { $Groks{$_} } keys %Groks; return wantarray ? @valid : "@valid"; } for my $feep (map { split } @_) { defined $Groks{$feep} || $sploder->("$feep is never a valid struct pwd field"); $cando &&= $Groks{$feep}; } return $cando; } sub _populate (@) { return unless @_; my $pwob = new(); # Any that haven't been pw_had are assumed on "all" platforms of # course, this may not be so, but you can't get here otherwise, # since the underlying core call already took exception to your # impudence. $pw_name = $pwob->name ( $_[0] ); $pw_passwd = $pwob->passwd ( $_[1] ) if pw_has("passwd"); $pw_uid = $pwob->uid ( $_[2] ); $pw_gid = $pwob->gid ( $_[3] ); if (pw_has("change")) { $pw_change = $pwob->change ( $_[4] ); } elsif (pw_has("age")) { $pw_age = $pwob->age ( $_[4] ); } elsif (pw_has("quota")) { $pw_quota = $pwob->quota ( $_[4] ); } if (pw_has("class")) { $pw_class = $pwob->class ( $_[5] ); } elsif (pw_has("comment")) { $pw_comment = $pwob->comment( $_[5] ); } $pw_gecos = $pwob->gecos ( $_[6] ) if pw_has("gecos"); $pw_dir = $pwob->dir ( $_[7] ); $pw_shell = $pwob->shell ( $_[8] ); $pw_expire = $pwob->expire ( $_[9] ) if pw_has("expire"); return $pwob; } sub getpwent ( ) { _populate(CORE::getpwent()) } sub getpwnam ($) { _populate(CORE::getpwnam(shift)) } sub getpwuid ($) { _populate(CORE::getpwuid(shift)) } sub getpw ($) { ($_[0] =~ /^\d+\z/s) ? &getpwuid : &getpwnam } _feature_init(); 1; __END__ package Attribute::Handlers; use 5.006; use Carp; use warnings; use strict; our $AUTOLOAD; our $VERSION = '1.01'; # remember to update version in POD! # $DB::single=1; my %symcache; sub findsym { my ($pkg, $ref, $type) = @_; return $symcache{$pkg,$ref} if $symcache{$pkg,$ref}; $type ||= ref($ref); no strict 'refs'; my $symtab = \%{$pkg."::"}; for ( keys %$symtab ) { for my $sym ( $$symtab{$_} ) { if (ref $sym && $sym == $ref) { return $symcache{$pkg,$ref} = \*{"$pkg:\:$_"}; } use strict; next unless ref ( \$sym ) eq 'GLOB'; return $symcache{$pkg,$ref} = \$sym if *{$sym}{$type} && *{$sym}{$type} == $ref; }} } my %validtype = ( VAR => [qw[SCALAR ARRAY HASH]], ANY => [qw[SCALAR ARRAY HASH CODE]], "" => [qw[SCALAR ARRAY HASH CODE]], SCALAR => [qw[SCALAR]], ARRAY => [qw[ARRAY]], HASH => [qw[HASH]], CODE => [qw[CODE]], ); my %lastattr; my @declarations; my %raw; my %phase; my %sigil = (SCALAR=>'$', ARRAY=>'@', HASH=>'%'); my $global_phase = 0; my %global_phases = ( BEGIN => 0, CHECK => 1, INIT => 2, END => 3, ); my @global_phases = qw(BEGIN CHECK INIT END); sub _usage_AH_ { croak "Usage: use $_[0] autotie => {AttrName => TieClassName,...}"; } my $qual_id = qr/^[_a-z]\w*(::[_a-z]\w*)*$/i; sub import { my $class = shift @_; return unless $class eq "Attribute::Handlers"; while (@_) { my $cmd = shift; if ($cmd =~ /^autotie((?:ref)?)$/) { my $tiedata = ($1 ? '$ref, ' : '') . '@$data'; my $mapping = shift; _usage_AH_ $class unless ref($mapping) eq 'HASH'; while (my($attr, $tieclass) = each %$mapping) { $tieclass =~ s/^([_a-z]\w*(::[_a-z]\w*)*)(.*)/$1/is; my $args = $3||'()'; _usage_AH_ $class unless $attr =~ $qual_id && $tieclass =~ $qual_id && eval "use base q\0$tieclass\0; 1"; if ($tieclass->isa('Exporter')) { local $Exporter::ExportLevel = 2; $tieclass->import(eval $args); } $attr =~ s/__CALLER__/caller(1)/e; $attr = caller()."::".$attr unless $attr =~ /::/; eval qq{ sub $attr : ATTR(VAR) { my (\$ref, \$data) = \@_[2,4]; my \$was_arrayref = ref \$data eq 'ARRAY'; \$data = [ \$data ] unless \$was_arrayref; my \$type = ref(\$ref)||"value (".(\$ref||"").")"; (\$type eq 'SCALAR')? tie \$\$ref,'$tieclass',$tiedata :(\$type eq 'ARRAY') ? tie \@\$ref,'$tieclass',$tiedata :(\$type eq 'HASH') ? tie \%\$ref,'$tieclass',$tiedata : die "Can't autotie a \$type\n" } 1 } or die "Internal error: $@"; } } else { croak "Can't understand $_"; } } } # On older perls, code attribute handlers run before the sub gets placed # in its package. Since the :ATTR handlers need to know the name of the # sub they're applied to, the name lookup (via findsym) needs to be # delayed: we do it immediately before we might need to find attribute # handlers from their name. However, on newer perls (which fix some # problems relating to attribute application), a sub gets placed in its # package before its attributes are processed. In this case, the # delayed name lookup might be too late, because the sub we're looking # for might have already been replaced. So we need to detect which way # round this perl does things, and time the name lookup accordingly. BEGIN { my $delayed; sub Attribute::Handlers::_TEST_::MODIFY_CODE_ATTRIBUTES { $delayed = \&Attribute::Handlers::_TEST_::t != $_[1]; return (); } sub Attribute::Handlers::_TEST_::t :T { } *_delayed_name_resolution = sub() { $delayed }; undef &Attribute::Handlers::_TEST_::MODIFY_CODE_ATTRIBUTES; undef &Attribute::Handlers::_TEST_::t; } sub _resolve_lastattr { return unless $lastattr{ref}; my $sym = findsym @lastattr{'pkg','ref'} or die "Internal error: $lastattr{pkg} symbol went missing"; my $name = *{$sym}{NAME}; warn "Declaration of $name attribute in package $lastattr{pkg} may clash with future reserved word\n" if $^W and $name !~ /[A-Z]/; foreach ( @{$validtype{$lastattr{type}}} ) { no strict 'refs'; *{"$lastattr{pkg}::_ATTR_${_}_${name}"} = $lastattr{ref}; } %lastattr = (); } sub AUTOLOAD { return if $AUTOLOAD =~ /::DESTROY$/; my ($class) = $AUTOLOAD =~ m/(.*)::/g; $AUTOLOAD =~ m/_ATTR_(.*?)_(.*)/ or croak "Can't locate class method '$AUTOLOAD' via package '$class'"; croak "Attribute handler '$2' doesn't handle $1 attributes"; } my $builtin = $] ge '5.027000' ? qr/lvalue|method|shared/ : qr/lvalue|method|locked|shared|unique/; sub _gen_handler_AH_() { return sub { _resolve_lastattr if _delayed_name_resolution; my ($pkg, $ref, @attrs) = @_; my (undef, $filename, $linenum) = caller 2; foreach (@attrs) { my ($attr, $data) = /^([a-z_]\w*)(?:[(](.*)[)])?$/is or next; if ($attr eq 'ATTR') { no strict 'refs'; $data ||= "ANY"; $raw{$ref} = $data =~ s/\s*,?\s*RAWDATA\s*,?\s*//; $phase{$ref}{BEGIN} = 1 if $data =~ s/\s*,?\s*(BEGIN)\s*,?\s*//; $phase{$ref}{INIT} = 1 if $data =~ s/\s*,?\s*(INIT)\s*,?\s*//; $phase{$ref}{END} = 1 if $data =~ s/\s*,?\s*(END)\s*,?\s*//; $phase{$ref}{CHECK} = 1 if $data =~ s/\s*,?\s*(CHECK)\s*,?\s*// || ! keys %{$phase{$ref}}; # Added for cleanup to not pollute next call. (%lastattr = ()), croak "Can't have two ATTR specifiers on one subroutine" if keys %lastattr; croak "Bad attribute type: ATTR($data)" unless $validtype{$data}; %lastattr=(pkg=>$pkg,ref=>$ref,type=>$data); _resolve_lastattr unless _delayed_name_resolution; } else { my $type = ref $ref; my $handler = $pkg->can("_ATTR_${type}_${attr}"); next unless $handler; my $decl = [$pkg, $ref, $attr, $data, $raw{$handler}, $phase{$handler}, $filename, $linenum]; foreach my $gphase (@global_phases) { _apply_handler_AH_($decl,$gphase) if $global_phases{$gphase} <= $global_phase; } if ($global_phase != 0) { # if _gen_handler_AH_ is being called after # CHECK it's for a lexical, so make sure # it didn't want to run anything later local $Carp::CarpLevel = 2; carp "Won't be able to apply END handler" if $phase{$handler}{END}; } else { push @declarations, $decl } } $_ = undef; } return grep {defined && !/$builtin/} @attrs; } } { no strict 'refs'; *{"Attribute::Handlers::UNIVERSAL::MODIFY_${_}_ATTRIBUTES"} = _gen_handler_AH_ foreach @{$validtype{ANY}}; } push @UNIVERSAL::ISA, 'Attribute::Handlers::UNIVERSAL' unless grep /^Attribute::Handlers::UNIVERSAL$/, @UNIVERSAL::ISA; sub _apply_handler_AH_ { my ($declaration, $phase) = @_; my ($pkg, $ref, $attr, $data, $raw, $handlerphase, $filename, $linenum) = @$declaration; return unless $handlerphase->{$phase}; # print STDERR "Handling $attr on $ref in $phase with [$data]\n"; my $type = ref $ref; my $handler = "_ATTR_${type}_${attr}"; my $sym = findsym($pkg, $ref); $sym ||= $type eq 'CODE' ? 'ANON' : 'LEXICAL'; no warnings; if (!$raw && defined($data)) { if ($data ne '') { my $evaled = eval("package $pkg; no warnings; no strict; local \$SIG{__WARN__}=sub{die}; [$data]"); $data = $evaled unless $@; } else { $data = undef } } $pkg->$handler($sym, (ref $sym eq 'GLOB' ? *{$sym}{ref $ref}||$ref : $ref), $attr, $data, $phase, $filename, $linenum, ); return 1; } { no warnings 'void'; CHECK { $global_phase++; _resolve_lastattr if _delayed_name_resolution; foreach my $decl (@declarations) { _apply_handler_AH_($decl, 'CHECK'); } } INIT { $global_phase++; foreach my $decl (@declarations) { _apply_handler_AH_($decl, 'INIT'); } } } END { $global_phase++; foreach my $decl (@declarations) { _apply_handler_AH_($decl, 'END'); } } 1; __END__ Encode::$_Name_ version 0.1 ======== NAME Encode::$_Name_ - SYNOPSIS use Encode::$_Name_; # ABSTRACT INSTALLATION To install this module type the following: perl Makefile.PL make make test make install DEPENDENCIES This module requires perl version 5.7.3 or later. COPYRIGHT AND LICENCE Copyright (C) 2002 Your Name This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself. # # This file is auto-generated by: # enc2xs version $_Version_ # $_Now_ # use 5.7.2; use strict; use ExtUtils::MakeMaker; use Config; # Please edit the following to the taste! my $name = '$_Name_'; my %tables = ( $_Name__t => [ $_TableFiles_ ], ); #### DO NOT EDIT BEYOND THIS POINT! require File::Spec; my ($enc2xs, $encode_h) = (); my @path_ext = (''); @path_ext = split(';', $ENV{PATHEXT}) if $^O eq 'MSWin32'; PATHLOOP: for my $d (@Config{qw/bin sitebin vendorbin/}, (split /$Config{path_sep}/o, $ENV{PATH})){ for my $f (qw/enc2xs enc2xs5.7.3/){ my $path = File::Spec->catfile($d, $f); for my $ext (@path_ext) { my $bin = "$path$ext"; -r "$bin" and $enc2xs = $bin and last PATHLOOP; } } } $enc2xs or die "enc2xs not found!"; print "enc2xs is $enc2xs\n"; my %encode_h = (); for my $d (@INC){ my $dir = File::Spec->catfile($d, "Encode"); my $file = File::Spec->catfile($dir, "encode.h"); -f $file and $encode_h{$dir} = -M $file; } %encode_h or die "encode.h not found!"; # find the latest one ($encode_h) = sort {$encode_h{$b} <=> $encode_h{$a}} keys %encode_h; print "encode.h is at $encode_h\n"; WriteMakefile( INC => "-I$encode_h", #### END_OF_HEADER -- DO NOT EDIT THIS LINE BY HAND! #### NAME => 'Encode::'.$name, VERSION_FROM => "$name.pm", OBJECT => '$(O_FILES)', 'dist' => { COMPRESS => 'gzip -9f', SUFFIX => 'gz', DIST_DEFAULT => 'all tardist', }, MAN3PODS => {}, PREREQ_PM => { 'Encode' => "1.41", }, # OS 390 winges about line numbers > 64K ??? XSOPT => '-nolinenumbers', ); package MY; sub post_initialize { my ($self) = @_; my %o; my $x = $self->{'OBJ_EXT'}; # Add the table O_FILES foreach my $e (keys %tables) { $o{$e.$x} = 1; } $o{"$name$x"} = 1; $self->{'O_FILES'} = [sort keys %o]; my @files = ("$name.xs"); $self->{'C'} = ["$name.c"]; # The next two lines to make MacPerl Happy -- dankogai via pudge $self->{SOURCE} .= " $name.c" if $^O eq 'MacOS' && $self->{SOURCE} !~ /\b$name\.c\b/; # $self->{'H'} = [$self->catfile($self->updir,'encode.h')]; my %xs; foreach my $table (sort keys %tables) { push (@{$self->{'C'}},"$table.c"); # Do NOT add $table.h etc. to H_FILES unless we own up as to how they # get built. foreach my $ext (qw($(OBJ_EXT) .c .h .exh .fnm)) { push (@files,$table.$ext); } } $self->{'XS'} = { "$name.xs" => "$name.c" }; $self->{'clean'}{'FILES'} .= join(' ',@files); open(XS,">$name.xs") || die "Cannot open $name.xs:$!"; print XS <<'END'; #include #include #include #include "encode.h" END foreach my $table (sort keys %tables) { print XS qq[#include "${table}.h"\n]; } print XS <<"END"; static void Encode_XSEncoding(pTHX_ encode_t *enc) { dSP; HV *stash = gv_stashpv("Encode::XS", TRUE); SV *iv = newSViv(PTR2IV(enc)); SV *sv = sv_bless(newRV_noinc(iv),stash); int i = 0; /* with the SvLEN() == 0 hack, PVX won't be freed. We cast away name's constness, in the hope that perl won't mess with it. */ assert(SvTYPE(iv) >= SVt_PV); assert(SvLEN(iv) == 0); SvFLAGS(iv) |= SVp_POK; SvPVX(iv) = (char*) enc->name[0]; PUSHMARK(sp); XPUSHs(sv); while (enc->name[i]) { const char *name = enc->name[i++]; XPUSHs(sv_2mortal(newSVpvn(name,strlen(name)))); } PUTBACK; call_pv("Encode::define_encoding",G_DISCARD); SvREFCNT_dec(sv); } MODULE = Encode::$name PACKAGE = Encode::$name PROTOTYPES: DISABLE BOOT: { END foreach my $table (sort keys %tables) { print XS qq[#include "${table}.exh"\n]; } print XS "}\n"; close(XS); return "# Built $name.xs\n\n"; } sub postamble { my $self = shift; my $dir = "."; # $self->catdir('Encode'); my $str = "# $name\$(OBJ_EXT) depends on .h and .exh files not .c files - but all written by enc2xs\n"; $str .= "$name.c : $name.xs "; foreach my $table (sort keys %tables) { $str .= " $table.c"; } $str .= "\n\n"; $str .= "$name\$(OBJ_EXT) : $name.c\n\n"; foreach my $table (sort keys %tables) { my $numlines = 1; my $lengthsofar = length($str); my $continuator = ''; $str .= "$table.c : Makefile.PL"; foreach my $file (@{$tables{$table}}) { $str .= $continuator.' '.$self->catfile($dir,$file); if ( length($str)-$lengthsofar > 128*$numlines ) { $continuator .= " \\\n\t"; $numlines++; } else { $continuator = ''; } } my $plib = $self->{PERL_CORE} ? '"-I$(PERL_LIB)"' : ''; my $ucopts = '-"Q"'; $str .= qq{\n\t\$(PERL) $plib $enc2xs $ucopts -o \$\@ -f $table.fnm\n\n}; open (FILELIST, ">$table.fnm") || die "Could not open $table.fnm: $!"; foreach my $file (@{$tables{$table}}) { print FILELIST $self->catfile($dir,$file) . "\n"; } close(FILELIST); } return $str; } # # $Id: Changes.e2x,v 2.0 2004/05/16 20:55:15 dankogai Exp $ # Revision history for Perl extension Encode::$_Name_. # 0.01 $_Now_ Autogenerated by enc2xs version $_Version_. # # Local demand-load module list # # You should not edit this file by hand! use "enc2xs -C" # package Encode::ConfigLocal; our $VERSION = $_LocalVer_; use strict; $_ModLines_ 1; package Encode::$_Name_; our $VERSION = "0.01"; use Encode; use XSLoader; XSLoader::load(__PACKAGE__,$VERSION); 1; __END__ =head1 NAME Encode::$_Name_ - New Encoding =head1 SYNOPSIS You got to fill this in! =head1 SEE ALSO L =cut use strict; # Adjust the number here! use Test::More tests => 2; BEGIN { use_ok('Encode'); use_ok('Encode::$_Name_'); } # Add more test here! use 5.008001; use strict; use warnings; package Parse::CPAN::Meta; # ABSTRACT: Parse META.yml and META.json CPAN metadata files our $VERSION = '2.150010'; use Exporter; use Carp 'croak'; our @ISA = qw/Exporter/; our @EXPORT_OK = qw/Load LoadFile/; sub load_file { my ($class, $filename) = @_; my $meta = _slurp($filename); if ($filename =~ /\.ya?ml$/) { return $class->load_yaml_string($meta); } elsif ($filename =~ /\.json$/) { return $class->load_json_string($meta); } else { $class->load_string($meta); # try to detect yaml/json } } sub load_string { my ($class, $string) = @_; if ( $string =~ /^---/ ) { # looks like YAML return $class->load_yaml_string($string); } elsif ( $string =~ /^\s*\{/ ) { # looks like JSON return $class->load_json_string($string); } else { # maybe doc-marker-free YAML return $class->load_yaml_string($string); } } sub load_yaml_string { my ($class, $string) = @_; my $backend = $class->yaml_backend(); my $data = eval { no strict 'refs'; &{"$backend\::Load"}($string) }; croak $@ if $@; return $data || {}; # in case document was valid but empty } sub load_json_string { my ($class, $string) = @_; require Encode; # load_json_string takes characters, decode_json expects bytes my $encoded = Encode::encode('UTF-8', $string, Encode::PERLQQ()); my $data = eval { $class->json_decoder()->can('decode_json')->($encoded) }; croak $@ if $@; return $data || {}; } sub yaml_backend { if ($ENV{PERL_CORE} or not defined $ENV{PERL_YAML_BACKEND} ) { _can_load( 'CPAN::Meta::YAML', 0.011 ) or croak "CPAN::Meta::YAML 0.011 is not available\n"; return "CPAN::Meta::YAML"; } else { my $backend = $ENV{PERL_YAML_BACKEND}; _can_load( $backend ) or croak "Could not load PERL_YAML_BACKEND '$backend'\n"; $backend->can("Load") or croak "PERL_YAML_BACKEND '$backend' does not implement Load()\n"; return $backend; } } sub json_decoder { if ($ENV{PERL_CORE}) { _can_load( 'JSON::PP' => 2.27300 ) or croak "JSON::PP 2.27300 is not available\n"; return 'JSON::PP'; } if (my $decoder = $ENV{CPAN_META_JSON_DECODER}) { _can_load( $decoder ) or croak "Could not load CPAN_META_JSON_DECODER '$decoder'\n"; $decoder->can('decode_json') or croak "No decode_json sub provided by CPAN_META_JSON_DECODER '$decoder'\n"; return $decoder; } return $_[0]->json_backend; } sub json_backend { if ($ENV{PERL_CORE}) { _can_load( 'JSON::PP' => 2.27300 ) or croak "JSON::PP 2.27300 is not available\n"; return 'JSON::PP'; } if (my $backend = $ENV{CPAN_META_JSON_BACKEND}) { _can_load( $backend ) or croak "Could not load CPAN_META_JSON_BACKEND '$backend'\n"; $backend->can('new') or croak "No constructor provided by CPAN_META_JSON_BACKEND '$backend'\n"; return $backend; } if (! $ENV{PERL_JSON_BACKEND} or $ENV{PERL_JSON_BACKEND} eq 'JSON::PP') { _can_load( 'JSON::PP' => 2.27300 ) or croak "JSON::PP 2.27300 is not available\n"; return 'JSON::PP'; } else { _can_load( 'JSON' => 2.5 ) or croak "JSON 2.5 is required for " . "\$ENV{PERL_JSON_BACKEND} = '$ENV{PERL_JSON_BACKEND}'\n"; return "JSON"; } } sub _slurp { require Encode; open my $fh, "<:raw", "$_[0]" ## no critic or die "can't open $_[0] for reading: $!"; my $content = do { local $/; <$fh> }; $content = Encode::decode('UTF-8', $content, Encode::PERLQQ()); return $content; } sub _can_load { my ($module, $version) = @_; (my $file = $module) =~ s{::}{/}g; $file .= ".pm"; return 1 if $INC{$file}; return 0 if exists $INC{$file}; # prior load failed eval { require $file; 1 } or return 0; if ( defined $version ) { eval { $module->VERSION($version); 1 } or return 0; } return 1; } # Kept for backwards compatibility only # Create an object from a file sub LoadFile ($) { ## no critic return Load(_slurp(shift)); } # Parse a document from a string. sub Load ($) { ## no critic require CPAN::Meta::YAML; my $object = eval { CPAN::Meta::YAML::Load(shift) }; croak $@ if $@; return $object; } 1; __END__ package Thread::Queue; use strict; use warnings; our $VERSION = '3.12'; $VERSION = eval $VERSION; use threads::shared 1.21; use Scalar::Util 1.10 qw(looks_like_number blessed reftype refaddr); # Carp errors from threads::shared calls should complain about caller our @CARP_NOT = ("threads::shared"); # Create a new queue possibly pre-populated with items sub new { my $class = shift; my @queue :shared = map { shared_clone($_) } @_; my %self :shared = ( 'queue' => \@queue ); return bless(\%self, $class); } # Add items to the tail of a queue sub enqueue { my $self = shift; lock(%$self); if ($$self{'ENDED'}) { require Carp; Carp::croak("'enqueue' method called on queue that has been 'end'ed"); } # Block if queue size exceeds any specified limit my $queue = $$self{'queue'}; cond_wait(%$self) while ($$self{'LIMIT'} && (@$queue >= $$self{'LIMIT'})); # Add items to queue, and then signal other threads push(@$queue, map { shared_clone($_) } @_) and cond_signal(%$self); } # Set or return the max. size for a queue sub limit : lvalue { my $self = shift; lock(%$self); $$self{'LIMIT'}; } # Return a count of the number of items on a queue sub pending { my $self = shift; lock(%$self); return if ($$self{'ENDED'} && ! @{$$self{'queue'}}); return scalar(@{$$self{'queue'}}); } # Indicate that no more data will enter the queue sub end { my $self = shift; lock(%$self); # No more data is coming $$self{'ENDED'} = 1; cond_signal(%$self); # Unblock possibly waiting threads } # Return 1 or more items from the head of a queue, blocking if needed sub dequeue { my $self = shift; lock(%$self); my $queue = $$self{'queue'}; my $count = @_ ? $self->_validate_count(shift) : 1; # Wait for requisite number of items cond_wait(%$self) while ((@$queue < $count) && ! $$self{'ENDED'}); # If no longer blocking, try getting whatever is left on the queue return $self->dequeue_nb($count) if ($$self{'ENDED'}); # Return single item if ($count == 1) { my $item = shift(@$queue); cond_signal(%$self); # Unblock possibly waiting threads return $item; } # Return multiple items my @items; push(@items, shift(@$queue)) for (1..$count); cond_signal(%$self); # Unblock possibly waiting threads return @items; } # Return items from the head of a queue with no blocking sub dequeue_nb { my $self = shift; lock(%$self); my $queue = $$self{'queue'}; my $count = @_ ? $self->_validate_count(shift) : 1; # Return single item if ($count == 1) { my $item = shift(@$queue); cond_signal(%$self); # Unblock possibly waiting threads return $item; } # Return multiple items my @items; for (1..$count) { last if (! @$queue); push(@items, shift(@$queue)); } cond_signal(%$self); # Unblock possibly waiting threads return @items; } # Return items from the head of a queue, blocking if needed up to a timeout sub dequeue_timed { my $self = shift; lock(%$self); my $queue = $$self{'queue'}; # Timeout may be relative or absolute my $timeout = @_ ? $self->_validate_timeout(shift) : -1; # Convert to an absolute time for use with cond_timedwait() if ($timeout < 32000000) { # More than one year $timeout += time(); } my $count = @_ ? $self->_validate_count(shift) : 1; # Wait for requisite number of items, or until timeout while ((@$queue < $count) && ! $$self{'ENDED'}) { last if (! cond_timedwait(%$self, $timeout)); } # Get whatever we need off the queue if available return $self->dequeue_nb($count); } # Return an item without removing it from a queue sub peek { my $self = shift; lock(%$self); my $index = @_ ? $self->_validate_index(shift) : 0; return $$self{'queue'}[$index]; } # Insert items anywhere into a queue sub insert { my $self = shift; lock(%$self); if ($$self{'ENDED'}) { require Carp; Carp::croak("'insert' method called on queue that has been 'end'ed"); } my $queue = $$self{'queue'}; my $index = $self->_validate_index(shift); return if (! @_); # Nothing to insert # Support negative indices if ($index < 0) { $index += @$queue; if ($index < 0) { $index = 0; } } # Dequeue items from $index onward my @tmp; while (@$queue > $index) { unshift(@tmp, pop(@$queue)) } # Add new items to the queue push(@$queue, map { shared_clone($_) } @_); # Add previous items back onto the queue push(@$queue, @tmp); cond_signal(%$self); # Unblock possibly waiting threads } # Remove items from anywhere in a queue sub extract { my $self = shift; lock(%$self); my $queue = $$self{'queue'}; my $index = @_ ? $self->_validate_index(shift) : 0; my $count = @_ ? $self->_validate_count(shift) : 1; # Support negative indices if ($index < 0) { $index += @$queue; if ($index < 0) { $count += $index; return if ($count <= 0); # Beyond the head of the queue return $self->dequeue_nb($count); # Extract from the head } } # Dequeue items from $index+$count onward my @tmp; while (@$queue > ($index+$count)) { unshift(@tmp, pop(@$queue)) } # Extract desired items my @items; unshift(@items, pop(@$queue)) while (@$queue > $index); # Add back any removed items push(@$queue, @tmp); cond_signal(%$self); # Unblock possibly waiting threads # Return single item return $items[0] if ($count == 1); # Return multiple items return @items; } ### Internal Methods ### # Check value of the requested index sub _validate_index { my $self = shift; my $index = shift; if (! defined($index) || ! looks_like_number($index) || (int($index) != $index)) { require Carp; my ($method) = (caller(1))[3]; my $class_name = ref($self); $method =~ s/$class_name\:://; $index = 'undef' if (! defined($index)); Carp::croak("Invalid 'index' argument ($index) to '$method' method"); } return $index; }; # Check value of the requested count sub _validate_count { my $self = shift; my $count = shift; if (! defined($count) || ! looks_like_number($count) || (int($count) != $count) || ($count < 1) || ($$self{'LIMIT'} && $count > $$self{'LIMIT'})) { require Carp; my ($method) = (caller(1))[3]; my $class_name = ref($self); $method =~ s/$class_name\:://; $count = 'undef' if (! defined($count)); if ($$self{'LIMIT'} && $count > $$self{'LIMIT'}) { Carp::croak("'count' argument ($count) to '$method' method exceeds queue size limit ($$self{'LIMIT'})"); } else { Carp::croak("Invalid 'count' argument ($count) to '$method' method"); } } return $count; }; # Check value of the requested timeout sub _validate_timeout { my $self = shift; my $timeout = shift; if (! defined($timeout) || ! looks_like_number($timeout)) { require Carp; my ($method) = (caller(1))[3]; my $class_name = ref($self); $method =~ s/$class_name\:://; $timeout = 'undef' if (! defined($timeout)); Carp::croak("Invalid 'timeout' argument ($timeout) to '$method' method"); } return $timeout; }; 1; package Thread::Semaphore; use strict; use warnings; our $VERSION = '2.13'; $VERSION = eval $VERSION; use threads::shared; use Scalar::Util 1.10 qw(looks_like_number); # Predeclarations for internal functions my ($validate_arg); # Create a new semaphore optionally with specified count (count defaults to 1) sub new { my $class = shift; my $val :shared = 1; if (@_) { $val = shift; if (! defined($val) || ! looks_like_number($val) || (int($val) != $val)) { require Carp; $val = 'undef' if (! defined($val)); Carp::croak("Semaphore initializer is not an integer: $val"); } } return bless(\$val, $class); } # Decrement a semaphore's count (decrement amount defaults to 1) sub down { my $sema = shift; my $dec = @_ ? $validate_arg->(shift) : 1; lock($$sema); cond_wait($$sema) until ($$sema >= $dec); $$sema -= $dec; } # Decrement a semaphore's count only if count >= decrement value # (decrement amount defaults to 1) sub down_nb { my $sema = shift; my $dec = @_ ? $validate_arg->(shift) : 1; lock($$sema); my $ok = ($$sema >= $dec); $$sema -= $dec if $ok; return $ok; } # Decrement a semaphore's count even if the count goes below 0 # (decrement amount defaults to 1) sub down_force { my $sema = shift; my $dec = @_ ? $validate_arg->(shift) : 1; lock($$sema); $$sema -= $dec; } # Decrement a semaphore's count with timeout # (timeout in seconds; decrement amount defaults to 1) sub down_timed { my $sema = shift; my $timeout = $validate_arg->(shift); my $dec = @_ ? $validate_arg->(shift) : 1; lock($$sema); my $abs = time() + $timeout; until ($$sema >= $dec) { return if !cond_timedwait($$sema, $abs); } $$sema -= $dec; return 1; } # Increment a semaphore's count (increment amount defaults to 1) sub up { my $sema = shift; my $inc = @_ ? $validate_arg->(shift) : 1; lock($$sema); ($$sema += $inc) > 0 and cond_broadcast($$sema); } ### Internal Functions ### # Validate method argument $validate_arg = sub { my $arg = shift; if (! defined($arg) || ! looks_like_number($arg) || (int($arg) != $arg) || ($arg < 1)) { require Carp; my ($method) = (caller(1))[3]; $method =~ s/Thread::Semaphore:://; $arg = 'undef' if (! defined($arg)); Carp::croak("Argument to semaphore method '$method' is not a positive integer: $arg"); } return $arg; }; 1; # You may distribute under the terms of either the GNU General Public License # or the Artistic License (the same terms as Perl itself) # # (C) Paul Evans, 2013-2016 -- leonerd@leonerd.org.uk package Future::Utils; use strict; use warnings; our $VERSION = '0.39'; use Exporter 'import'; # Can't import the one from Exporter as it relies on package inheritance sub export_to_level { my $pkg = shift; local $Exporter::ExportLevel = 1 + shift; $pkg->import(@_); } our @EXPORT_OK = qw( call call_with_escape repeat try_repeat try_repeat_until_success repeat_until_success fmap fmap_concat fmap1 fmap_scalar fmap0 fmap_void ); use Carp; our @CARP_NOT = qw( Future ); use Future; sub call(&) { my ( $code ) = @_; return Future->call( $code ); } sub call_with_escape(&) { my ( $code ) = @_; my $escape_f = Future->new; return Future->wait_any( Future->call( $code, $escape_f ), $escape_f, ); } sub _repeat { my ( $code, $return, $trialp, $cond, $sense, $is_try ) = @_; my $prev = $$trialp; while(1) { my $trial = $$trialp ||= Future->call( $code, $prev ); $prev = $trial; if( !$trial->is_ready ) { # defer $return ||= $trial->new; $trial->on_ready( sub { return if $$trialp->is_cancelled; _repeat( $code, $return, $trialp, $cond, $sense, $is_try ); }); return $return; } my $stop; if( not eval { $stop = !$cond->( $trial ) ^ $sense; 1 } ) { $return ||= $trial->new; $return->fail( $@ ); return $return; } if( $stop ) { # Return result $return ||= $trial->new; $trial->on_done( $return ); $trial->on_fail( $return ); return $return; } if( !$is_try and $trial->failure ) { carp "Using Future::Utils::repeat to retry a failure is deprecated; use try_repeat instead"; } # redo undef $$trialp; } } sub repeat(&@) { my $code = shift; my %args = @_; # This makes it easier to account for other conditions defined($args{while}) + defined($args{until}) == 1 or defined($args{foreach}) or defined($args{generate}) or croak "Expected one of 'while', 'until', 'foreach' or 'generate'"; if( $args{foreach} ) { $args{generate} and croak "Cannot use both 'foreach' and 'generate'"; my $array = delete $args{foreach}; $args{generate} = sub { @$array ? shift @$array : (); }; } if( $args{generate} ) { my $generator = delete $args{generate}; my $otherwise = delete $args{otherwise}; # TODO: This is slightly messy as this lexical is captured by both # blocks of code. Can we do better somehow? my $done; my $orig_code = $code; $code = sub { my ( $last_trial_f ) = @_; my $again = my ( $value ) = $generator->( $last_trial_f ); if( $again ) { unshift @_, $value; goto &$orig_code; } $done++; if( $otherwise ) { goto &$otherwise; } else { return $last_trial_f || Future->done; } }; if( my $orig_while = delete $args{while} ) { $args{while} = sub { $orig_while->( $_[0] ) and !$done; }; } elsif( my $orig_until = delete $args{until} ) { $args{while} = sub { !$orig_until->( $_[0] ) and !$done; }; } else { $args{while} = sub { !$done }; } } my $future = $args{return}; my $trial; $args{while} and $future = _repeat( $code, $future, \$trial, $args{while}, 0, $args{try} ); $args{until} and $future = _repeat( $code, $future, \$trial, $args{until}, 1, $args{try} ); $future->on_cancel( sub { $trial->cancel } ); return $future; } sub try_repeat(&@) { # defeat prototype &repeat( @_, try => 1 ); } sub try_repeat_until_success(&@) { my $code = shift; my %args = @_; # TODO: maybe merge while/until conditions one day... defined($args{while}) or defined($args{until}) and croak "Cannot pass 'while' or 'until' to try_repeat_until_success"; # defeat prototype &try_repeat( $code, while => sub { shift->failure }, %args ); } # Legacy name *repeat_until_success = \&try_repeat_until_success; # This function is invoked in two circumstances: # a) to create an item Future in a slot, # b) once a non-immediate item Future is complete, to check its results # It can tell which circumstance by whether the slot itself is defined or not sub _fmap_slot { my ( $slots, undef, $code, $generator, $collect, $results, $return ) = @_; SLOT: while(1) { # Capture args each call because we mutate them my ( undef, $idx ) = my @args = @_; unless( $slots->[$idx] ) { # No item Future yet (case a), so create one my $item; unless( ( $item ) = $generator->() ) { # All out of items, so now just wait for the slots to be finished undef $slots->[$idx]; defined and return $return for @$slots; # All the slots are done $return ||= Future->new; $return->done( @$results ); return $return; } my $f = $slots->[$idx] = Future->call( $code, local $_ = $item ); if( $collect eq "array" ) { push @$results, my $r = []; $f->on_done( sub { @$r = @_ }); } elsif( $collect eq "scalar" ) { push @$results, undef; my $r = \$results->[-1]; $f->on_done( sub { $$r = $_[0] }); } } my $f = $slots->[$idx]; # Slot is non-immediate; arrange for us to be invoked again later when it's ready if( !$f->is_ready ) { $args[-1] = ( $return ||= $f->new ); $f->on_done( sub { _fmap_slot( @args ) } ); $f->on_fail( $return ); # Try looking for more that might be ready my $i = $idx + 1; while( $i != $idx ) { $i++; $i %= @$slots; next if defined $slots->[$i]; $_[1] = $i; redo SLOT; } return $return; } # Either we've been invoked again (case b), or the immediate Future was # already ready. if( $f->failure ) { $return ||= $f->new; $return->fail( $f->failure ); return $return; } undef $slots->[$idx]; # next } } sub _fmap { my $code = shift; my %args = @_; my $concurrent = $args{concurrent} || 1; my @slots; my $results = []; my $future = $args{return}; my $generator; if( $generator = $args{generate} ) { # OK } elsif( my $array = $args{foreach} ) { $generator = sub { return unless @$array; shift @$array }; } else { croak "Expected either 'generate' or 'foreach'"; } # If any of these immediately fail, don't bother continuing foreach my $idx ( 0 .. $concurrent-1 ) { $future = _fmap_slot( \@slots, $idx, $code, $generator, $args{collect}, $results, $future ); last if $future->is_ready; } $future->on_fail( sub { !defined $_ or $_->is_ready or $_->cancel for @slots; }); $future->on_cancel( sub { !defined $_ or $_->is_ready or $_->cancel for @slots; }); return $future; } sub fmap_concat(&@) { my $code = shift; my %args = @_; _fmap( $code, %args, collect => "array" )->then( sub { return Future->done( map { @$_ } @_ ); }); } *fmap = \&fmap_concat; sub fmap_scalar(&@) { my $code = shift; my %args = @_; _fmap( $code, %args, collect => "scalar" ) } *fmap1 = \&fmap_scalar; sub fmap_void(&@) { my $code = shift; my %args = @_; _fmap( $code, %args, collect => "void" ) } *fmap0 = \&fmap_void; 0x55AA; # You may distribute under the terms of either the GNU General Public License # or the Artistic License (the same terms as Perl itself) # # (C) Paul Evans, 2016-2017 -- leonerd@leonerd.org.uk package Future::Mutex; use strict; use warnings; our $VERSION = '0.39'; use Future; sub new { my $class = shift; my %params = @_; return bless { avail => $params{count} // 1, queue => [], }, $class; } sub enter { my $self = shift; my ( $code ) = @_; my $down_f; if( $self->{avail} ) { $self->{avail}--; $down_f = Future->done; } else { push @{ $self->{queue} }, $down_f = Future->new; } my $up = sub { if( my $next_f = shift @{ $self->{queue} } ) { $next_f->done; } else { $self->{avail}++; } }; $down_f->then( $code )->on_ready( $up ); } sub available { my $self = shift; return $self->{avail}; } 0x55AA; # Time-stamp: "2004-10-06 23:26:33 ADT" # Sean M. Burke require 5.000; package I18N::LangTags; use strict; require Exporter; our @ISA = qw(Exporter); our @EXPORT = qw(); our @EXPORT_OK = qw(is_language_tag same_language_tag extract_language_tags super_languages similarity_language_tag is_dialect_of locale2language_tag alternate_language_tags encode_language_tag panic_languages implicate_supers implicate_supers_strictly ); our %EXPORT_TAGS = ('ALL' => \@EXPORT_OK); our $VERSION = "0.43"; our %Panic; sub uniq { my %seen; return grep(!($seen{$_}++), @_); } # a util function ########################################################################### sub is_language_tag { ## Changes in the language tagging standards may have to be reflected here. my($tag) = lc($_[0]); return 0 if $tag eq "i" or $tag eq "x"; # Bad degenerate cases that the following # regexp would erroneously let pass return $tag =~ /^(?: # First subtag [xi] | [a-z]{2,3} ) (?: # Subtags thereafter - # separator [a-z0-9]{1,8} # subtag )* $/xs ? 1 : 0; } ########################################################################### sub extract_language_tags { ## Changes in the language tagging standards may have to be reflected here. my($text) = $_[0] =~ m/(.+)/ # to make for an untainted result ? $1 : '' ; return grep(!m/^[ixIX]$/s, # 'i' and 'x' aren't good tags $text =~ m/ \b (?: # First subtag [iIxX] | [a-zA-Z]{2,3} ) (?: # Subtags thereafter - # separator [a-zA-Z0-9]{1,8} # subtag )* \b /xsg ); } ########################################################################### sub same_language_tag { my $el1 = &encode_language_tag($_[0]); return 0 unless defined $el1; # this avoids the problem of # encode_language_tag($lang1) eq and encode_language_tag($lang2) # being true if $lang1 and $lang2 are both undef return $el1 eq &encode_language_tag($_[1]) ? 1 : 0; } ########################################################################### sub similarity_language_tag { my $lang1 = &encode_language_tag($_[0]); my $lang2 = &encode_language_tag($_[1]); # And encode_language_tag takes care of the whole # no-nyn==nn, i-hakka==zh-hakka, etc, things # NB: (i-sil-...)? (i-sgn-...)? return undef if !defined($lang1) and !defined($lang2); return 0 if !defined($lang1) or !defined($lang2); my @l1_subtags = split('-', $lang1); my @l2_subtags = split('-', $lang2); my $similarity = 0; while(@l1_subtags and @l2_subtags) { if(shift(@l1_subtags) eq shift(@l2_subtags)) { ++$similarity; } else { last; } } return $similarity; } ########################################################################### sub is_dialect_of { my $lang1 = &encode_language_tag($_[0]); my $lang2 = &encode_language_tag($_[1]); return undef if !defined($lang1) and !defined($lang2); return 0 if !defined($lang1) or !defined($lang2); return 1 if $lang1 eq $lang2; return 0 if length($lang1) < length($lang2); $lang1 .= '-'; $lang2 .= '-'; return (substr($lang1, 0, length($lang2)) eq $lang2) ? 1 : 0; } ########################################################################### sub super_languages { my $lang1 = $_[0]; return() unless defined($lang1) && &is_language_tag($lang1); # a hack for those annoying new (2001) tags: $lang1 =~ s/^nb\b/no-bok/i; # yes, backwards $lang1 =~ s/^nn\b/no-nyn/i; # yes, backwards $lang1 =~ s/^[ix](-hakka\b)/zh$1/i; # goes the right way # i-hakka-bork-bjork-bjark => zh-hakka-bork-bjork-bjark my @l1_subtags = split('-', $lang1); ## Changes in the language tagging standards may have to be reflected here. # NB: (i-sil-...)? my @supers = (); foreach my $bit (@l1_subtags) { push @supers, scalar(@supers) ? ($supers[-1] . '-' . $bit) : $bit; } pop @supers if @supers; shift @supers if @supers && $supers[0] =~ m<^[iIxX]$>s; return reverse @supers; } ########################################################################### sub locale2language_tag { my $lang = $_[0] =~ m/(.+)/ # to make for an untainted result ? $1 : '' ; return $lang if &is_language_tag($lang); # like "en" $lang =~ tr<_><->; # "en_US" -> en-US $lang =~ s<(?:[\.\@][-_a-zA-Z0-9]+)+$><>s; # "en_US.ISO8859-1" -> en-US # it_IT.utf8@euro => it-IT return $lang if &is_language_tag($lang); return; } ########################################################################### sub encode_language_tag { # Only similarity_language_tag() is allowed to analyse encodings! ## Changes in the language tagging standards may have to be reflected here. my($tag) = $_[0] || return undef; return undef unless &is_language_tag($tag); # For the moment, these legacy variances are few enough that # we can just handle them here with regexps. $tag =~ s/^iw\b/he/i; # Hebrew $tag =~ s/^in\b/id/i; # Indonesian $tag =~ s/^cre\b/cr/i; # Cree $tag =~ s/^jw\b/jv/i; # Javanese $tag =~ s/^[ix]-lux\b/lb/i; # Luxemburger $tag =~ s/^[ix]-navajo\b/nv/i; # Navajo $tag =~ s/^ji\b/yi/i; # Yiddish # SMB 2003 -- Hm. There's a bunch of new XXX->YY variances now, # but maybe they're all so obscure I can ignore them. "Obscure" # meaning either that the language is obscure, and/or that the # XXX form was extant so briefly that it's unlikely it was ever # used. I hope. # # These go FROM the simplex to complex form, to get # similarity-comparison right. And that's okay, since # similarity_language_tag is the only thing that # analyzes our output. $tag =~ s/^[ix]-hakka\b/zh-hakka/i; # Hakka $tag =~ s/^nb\b/no-bok/i; # BACKWARDS for Bokmal $tag =~ s/^nn\b/no-nyn/i; # BACKWARDS for Nynorsk $tag =~ s/^[xiXI]-//s; # Just lop off any leading "x/i-" return "~" . uc($tag); } #-------------------------------------------------------------------------- my %alt = qw( i x x i I X X I ); sub alternate_language_tags { my $tag = $_[0]; return() unless &is_language_tag($tag); my @em; # push 'em real goood! # For the moment, these legacy variances are few enough that # we can just handle them here with regexps. if( $tag =~ m/^[ix]-hakka\b(.*)/i) {push @em, "zh-hakka$1"; } elsif($tag =~ m/^zh-hakka\b(.*)/i) { push @em, "x-hakka$1", "i-hakka$1"; } elsif($tag =~ m/^he\b(.*)/i) { push @em, "iw$1"; } elsif($tag =~ m/^iw\b(.*)/i) { push @em, "he$1"; } elsif($tag =~ m/^in\b(.*)/i) { push @em, "id$1"; } elsif($tag =~ m/^id\b(.*)/i) { push @em, "in$1"; } elsif($tag =~ m/^[ix]-lux\b(.*)/i) { push @em, "lb$1"; } elsif($tag =~ m/^lb\b(.*)/i) { push @em, "i-lux$1", "x-lux$1"; } elsif($tag =~ m/^[ix]-navajo\b(.*)/i) { push @em, "nv$1"; } elsif($tag =~ m/^nv\b(.*)/i) { push @em, "i-navajo$1", "x-navajo$1"; } elsif($tag =~ m/^yi\b(.*)/i) { push @em, "ji$1"; } elsif($tag =~ m/^ji\b(.*)/i) { push @em, "yi$1"; } elsif($tag =~ m/^nb\b(.*)/i) { push @em, "no-bok$1"; } elsif($tag =~ m/^no-bok\b(.*)/i) { push @em, "nb$1"; } elsif($tag =~ m/^nn\b(.*)/i) { push @em, "no-nyn$1"; } elsif($tag =~ m/^no-nyn\b(.*)/i) { push @em, "nn$1"; } push @em, $alt{$1} . $2 if $tag =~ /^([XIxi])(-.+)/; return @em; } ########################################################################### { # Init %Panic... my @panic = ( # MUST all be lowercase! # Only large ("national") languages make it in this list. # If you, as a user, are so bizarre that the /only/ language # you claim to accept is Galician, then no, we won't do you # the favor of providing Catalan as a panic-fallback for # you. Because if I start trying to add "little languages" in # here, I'll just go crazy. # Scandinavian lgs. All based on opinion and hearsay. 'sv' => [qw(nb no da nn)], 'da' => [qw(nb no sv nn)], # I guess [qw(no nn nb)], [qw(no nn nb sv da)], 'is' => [qw(da sv no nb nn)], 'fo' => [qw(da is no nb nn sv)], # I guess # I think this is about the extent of tolerable intelligibility # among large modern Romance languages. 'pt' => [qw(es ca it fr)], # Portuguese, Spanish, Catalan, Italian, French 'ca' => [qw(es pt it fr)], 'es' => [qw(ca it fr pt)], 'it' => [qw(es fr ca pt)], 'fr' => [qw(es it ca pt)], # Also assume that speakers of the main Indian languages prefer # to read/hear Hindi over English [qw( as bn gu kn ks kok ml mni mr ne or pa sa sd te ta ur )] => 'hi', # Assamese, Bengali, Gujarati, [Hindi,] Kannada (Kanarese), Kashmiri, # Konkani, Malayalam, Meithei (Manipuri), Marathi, Nepali, Oriya, # Punjabi, Sanskrit, Sindhi, Telugu, Tamil, and Urdu. 'hi' => [qw(bn pa as or)], # I welcome finer data for the other Indian languages. # E.g., what should Oriya's list be, besides just Hindi? # And the panic languages for English is, of course, nil! # My guesses at Slavic intelligibility: ([qw(ru be uk)]) x 2, # Russian, Belarusian, Ukranian ([qw(sr hr bs)]) x 2, # Serbian, Croatian, Bosnian 'cs' => 'sk', 'sk' => 'cs', # Czech + Slovak 'ms' => 'id', 'id' => 'ms', # Malay + Indonesian 'et' => 'fi', 'fi' => 'et', # Estonian + Finnish #?? 'lo' => 'th', 'th' => 'lo', # Lao + Thai ); my($k,$v); while(@panic) { ($k,$v) = splice(@panic,0,2); foreach my $k (ref($k) ? @$k : $k) { foreach my $v (ref($v) ? @$v : $v) { push @{$Panic{$k} ||= []}, $v unless $k eq $v; } } } } sub panic_languages { # When in panic or in doubt, run in circles, scream, and shout! my(@out, %seen); foreach my $t (@_) { next unless $t; next if $seen{$t}++; # so we don't return it or hit it again # push @out, super_languages($t); # nah, keep that separate push @out, @{ $Panic{lc $t} || next }; } return grep !$seen{$_}++, @out, 'en'; } #--------------------------------------------------------------------------- #--------------------------------------------------------------------------- sub implicate_supers { my @languages = grep is_language_tag($_), @_; my %seen_encoded; foreach my $lang (@languages) { $seen_encoded{ I18N::LangTags::encode_language_tag($lang) } = 1 } my(@output_languages); foreach my $lang (@languages) { push @output_languages, $lang; foreach my $s ( I18N::LangTags::super_languages($lang) ) { # Note that super_languages returns the longest first. last if $seen_encoded{ I18N::LangTags::encode_language_tag($s) }; push @output_languages, $s; } } return uniq( @output_languages ); } sub implicate_supers_strictly { my @tags = grep is_language_tag($_), @_; return uniq( @_, map super_languages($_), @_ ); } ########################################################################### 1; __END__ package I18N::Collate; use strict; our $VERSION = '1.02'; # I18N::Collate.pm # # Author: Jarkko Hietaniemi > # Helsinki University of Technology, Finland # # Acks: Guy Decoux > understood # overloading magic much deeper than I and told # how to cut the size of this code by more than half. # (my first version did overload all of lt gt eq le ge cmp) # # Purpose: compare 8-bit scalar data according to the current locale # # Requirements: Perl5 POSIX::setlocale() and POSIX::strxfrm() # # Exports: setlocale 1) # collate_xfrm 2) # # Overloads: cmp # 3) # # Usage: use I18N::Collate; # setlocale(LC_COLLATE, 'locale-of-your-choice'); # 4) # $s1 = I18N::Collate->("scalar_data_1"); # $s2 = I18N::Collate->("scalar_data_2"); # # now you can compare $s1 and $s2: $s1 le $s2 # to extract the data itself, you need to deref: $$s1 # # Notes: # 1) this uses POSIX::setlocale # 2) the basic collation conversion is done by strxfrm() which # terminates at NUL characters being a decent C routine. # collate_xfrm handles embedded NUL characters gracefully. # 3) due to cmp and overload magic, lt le eq ge gt work also # 4) the available locales depend on your operating system; # try whether "locale -a" shows them or man pages for # "locale" or "nlsinfo" work or the more direct # approach "ls /usr/lib/nls/loc" or "ls /usr/lib/nls". # Not all the locales that your vendor supports # are necessarily installed: please consult your # operating system's documentation. # The locale names are probably something like # 'xx_XX.(ISO)?8859-N' or 'xx_XX.(ISO)?8859N', # for example 'fr_CH.ISO8859-1' is the Swiss (CH) # variant of French (fr), ISO Latin (8859) 1 (-1) # which is the Western European character set. # # Updated: 19961005 # # --- use POSIX qw(strxfrm LC_COLLATE); use warnings::register; require Exporter; our @ISA = qw(Exporter); our @EXPORT = qw(collate_xfrm setlocale LC_COLLATE); our @EXPORT_OK = qw(); use overload qw( fallback 1 cmp collate_cmp ); our($LOCALE, $C); our $please_use_I18N_Collate_even_if_deprecated = 0; sub new { my $new = $_[1]; if (warnings::enabled() && $] >= 5.003_06) { unless ($please_use_I18N_Collate_even_if_deprecated) { warnings::warn <<___EOD___; *** WARNING: starting from the Perl version 5.003_06 the I18N::Collate interface for comparing 8-bit scalar data according to the current locale HAS BEEN DEPRECATED That is, please do not use it anymore for any new applications and please migrate the old applications away from it because its functionality was integrated into the Perl core language in the release 5.003_06. See the perllocale manual page for further information. *** ___EOD___ $please_use_I18N_Collate_even_if_deprecated++; } } bless \$new; } sub setlocale { my ($category, $locale) = @_[0,1]; POSIX::setlocale($category, $locale) if (defined $category); # the current $LOCALE $LOCALE = $locale || $ENV{'LC_COLLATE'} || $ENV{'LC_ALL'} || ''; } sub C { my $s = ${$_[0]}; $C->{$LOCALE}->{$s} = collate_xfrm($s) unless (defined $C->{$LOCALE}->{$s}); # cache when met $C->{$LOCALE}->{$s}; } sub collate_xfrm { my $s = $_[0]; my $x = ''; for (split(/(\000+)/, $s)) { $x .= (/^\000/) ? $_ : strxfrm("$_\000"); } $x; } sub collate_cmp { &C($_[0]) cmp &C($_[1]); } # init $LOCALE &I18N::Collate::setlocale(); 1; # keep require happy require 5; package I18N::LangTags::List; # Time-stamp: "2004-10-06 23:26:21 ADT" use strict; our (%Name, %Is_Disrec, $Debug); our $VERSION = '0.40'; # POD at the end. #---------------------------------------------------------------------- { # read the table out of our own POD! my $seeking = 1; my $count = 0; my($disrec,$tag,$name); my $last_name = ''; while() { if($seeking) { $seeking = 0 if m/=for woohah/; } elsif( ($disrec, $tag, $name) = m/(\[?)\{([-0-9a-zA-Z]+)\}(?:\s*:)?\s*([^\[\]]+)/ ) { $name =~ s/\s*[;\.]*\s*$//g; next unless $name; ++$count; print "<$tag> <$name>\n" if $Debug; $last_name = $Name{$tag} = $name; $Is_Disrec{$tag} = 1 if $disrec; } elsif (m/[Ff]ormerly \"([-a-z0-9]+)\"/) { $Name{$1} = "$last_name (old tag)" if $last_name; $Is_Disrec{$1} = 1; } } die "No tags read??" unless $count; } #---------------------------------------------------------------------- sub name { my $tag = lc($_[0] || return); $tag =~ s/^\s+//s; $tag =~ s/\s+$//s; my $alt; if($tag =~ m/^x-(.+)/) { $alt = "i-$1"; } elsif($tag =~ m/^i-(.+)/) { $alt = "x-$1"; } else { $alt = ''; } my $subform = ''; my $name = ''; print "Input: {$tag}\n" if $Debug; while(length $tag) { last if $name = $Name{$tag}; last if $name = $Name{$alt}; if($tag =~ s/(-[a-z0-9]+)$//s) { print "Shaving off: $1 leaving $tag\n" if $Debug; $subform = "$1$subform"; # and loop around again $alt =~ s/(-[a-z0-9]+)$//s && $Debug && print " alt -> $alt\n"; } else { # we're trying to pull a subform off a primary tag. TILT! print "Aborting on: {$name}{$subform}\n" if $Debug; last; } } print "Output: {$name}{$subform}\n" if $Debug; return unless $name; # Failure return $name unless $subform; # Exact match $subform =~ s/^-//s; $subform =~ s/-$//s; return "$name (Subform \"$subform\")"; } #-------------------------------------------------------------------------- sub is_decent { my $tag = lc($_[0] || return 0); #require I18N::LangTags; return 0 unless $tag =~ /^(?: # First subtag [xi] | [a-z]{2,3} ) (?: # Subtags thereafter - # separator [a-z0-9]{1,8} # subtag )* $/xs; my @supers = (); foreach my $bit (split('-', $tag)) { push @supers, scalar(@supers) ? ($supers[-1] . '-' . $bit) : $bit; } return 0 unless @supers; shift @supers if $supers[0] =~ m<^(i|x|sgn)$>s; return 0 unless @supers; foreach my $f ($tag, @supers) { return 0 if $Is_Disrec{$f}; return 2 if $Name{$f}; # so that decent subforms of indecent tags are decent } return 2 if $Name{$tag}; # not only is it decent, it's known! return 1; } #-------------------------------------------------------------------------- 1; __DATA__ # To generate a list of just the two and three-letter codes: #!/usr/local/bin/perl -w require 5; # Time-stamp: "2001-03-13 21:53:39 MST" # Sean M. Burke, sburke@cpan.org # This program is for generating the language_codes.txt file use strict; use LWP::Simple; use HTML::TreeBuilder 3.10; my $root = HTML::TreeBuilder->new(); my $url = 'http://lcweb.loc.gov/standards/iso639-2/bibcodes.html'; $root->parse(get($url) || die "Can't get $url"); $root->eof(); my @codes; foreach my $tr ($root->find_by_tag_name('tr')) { my @f = map $_->as_text(), $tr->content_list(); #print map("<$_> ", @f), "\n"; next unless @f == 5; pop @f; # nix the French name next if $f[-1] eq 'Language Name (English)'; # it's a header line my $xx = splice(@f, 2,1); # pull out the two-letter code $f[-1] =~ s/^\s+//; $f[-1] =~ s/\s+$//; if($xx =~ m/[a-zA-Z]/) { # there's a two-letter code for it push @codes, [ lc($f[-1]), "$xx\t$f[-1]\n" ]; } else { # print the three-letter codes. if($f[0] eq $f[1]) { push @codes, [ lc($f[-1]), "$f[1]\t$f[2]\n" ]; } else { # shouldn't happen push @codes, [ lc($f[-1]), "@f !!!!!!!!!!\n" ]; } } } print map $_->[1], sort {; $a->[0] cmp $b->[0] } @codes; print "[ based on $url\n at ", scalar(localtime), "]\n", "[Note: doesn't include IANA-registered codes.]\n"; exit; __END__ # Time-stamp: "2004-06-20 21:47:55 ADT" require 5; package I18N::LangTags::Detect; use strict; our ( $MATCH_SUPERS, $USING_LANGUAGE_TAGS, $USE_LITERALS, $MATCH_SUPERS_TIGHTLY); BEGIN { unless(defined &DEBUG) { *DEBUG = sub () {0} } } # define the constant 'DEBUG' at compile-time our $VERSION = "1.07"; our @ISA = (); use I18N::LangTags qw(alternate_language_tags locale2language_tag); sub _uniq { my %seen; return grep(!($seen{$_}++), @_); } sub _normalize { my(@languages) = map lc($_), grep $_, map {; $_, alternate_language_tags($_) } @_; return _uniq(@languages) if wantarray; return $languages[0]; } #--------------------------------------------------------------------------- # The extent of our functional interface: sub detect () { return __PACKAGE__->ambient_langprefs; } #=========================================================================== sub ambient_langprefs { # always returns things untainted my $base_class = $_[0]; return $base_class->http_accept_langs if length( $ENV{'REQUEST_METHOD'} || '' ); # I'm a CGI # it's off in its own routine because it's complicated # Not running as a CGI: try to puzzle out from the environment my @languages; foreach my $envname (qw( LANGUAGE LC_ALL LC_MESSAGES LANG )) { next unless $ENV{$envname}; DEBUG and print "Noting \$$envname: $ENV{$envname}\n"; push @languages, map locale2language_tag($_), # if it's a lg tag, fine, pass thru (untainted) # if it's a locale ID, try converting to a lg tag (untainted), # otherwise nix it. split m/[,:]/, $ENV{$envname} ; last; # first one wins } if($ENV{'IGNORE_WIN32_LOCALE'}) { # no-op } elsif(&_try_use('Win32::Locale')) { # If we have that module installed... push @languages, Win32::Locale::get_language() || '' if defined &Win32::Locale::get_language; } return _normalize @languages; } #--------------------------------------------------------------------------- sub http_accept_langs { # Deal with HTTP "Accept-Language:" stuff. Hassle. # This code is more lenient than RFC 3282, which you must read. # Hm. Should I just move this into I18N::LangTags at some point? no integer; my $in = (@_ > 1) ? $_[1] : $ENV{'HTTP_ACCEPT_LANGUAGE'}; # (always ends up untainting) return() unless defined $in and length $in; $in =~ s/\([^\)]*\)//g; # nix just about any comment if( $in =~ m/^\s*([a-zA-Z][-a-zA-Z]+)\s*$/s ) { # Very common case: just one language tag return _normalize $1; } elsif( $in =~ m/^\s*[a-zA-Z][-a-zA-Z]+(?:\s*,\s*[a-zA-Z][-a-zA-Z]+)*\s*$/s ) { # Common case these days: just "foo, bar, baz" return _normalize( $in =~ m/([a-zA-Z][-a-zA-Z]+)/g ); } # Else it's complicated... $in =~ s/\s+//g; # Yes, we can just do without the WS! my @in = $in =~ m/([^,]+)/g; my %pref; my $q; foreach my $tag (@in) { next unless $tag =~ m/^([a-zA-Z][-a-zA-Z]+) (?: ;q= ( \d* # a bit too broad of a RE, but so what. (?: \.\d+ )? ) )? $ /sx ; $q = (defined $2 and length $2) ? $2 : 1; #print "$1 with q=$q\n"; push @{ $pref{$q} }, lc $1; } return _normalize( # Read off %pref, in descending key order... map @{$pref{$_}}, sort {$b <=> $a} keys %pref ); } #=========================================================================== my %tried = (); # memoization of whether we've used this module, or found it unusable. sub _try_use { # Basically a wrapper around "require Modulename" # "Many men have tried..." "They tried and failed?" "They tried and died." return $tried{$_[0]} if exists $tried{$_[0]}; # memoization my $module = $_[0]; # ASSUME sane module name! { no strict 'refs'; no warnings 'once'; return($tried{$module} = 1) if %{$module . "::Lexicon"} or @{$module . "::ISA"}; # weird case: we never use'd it, but there it is! } print " About to use $module ...\n" if DEBUG; { local $SIG{'__DIE__'}; local @INC = @INC; pop @INC if $INC[-1] eq '.'; eval "require $module"; # used to be "use $module", but no point in that. } if($@) { print "Error using $module \: $@\n" if DEBUG > 1; return $tried{$module} = 0; } else { print " OK, $module is used\n" if DEBUG; return $tried{$module} = 1; } } #--------------------------------------------------------------------------- 1; __END__ # a tip: Put a bit of chopped up pickled ginger in your salad. It's tasty! # Pod::PlainText -- Convert POD data to formatted ASCII text. # $Id: Text.pm,v 2.1 1999/09/20 11:53:33 eagle Exp $ # # Copyright 1999-2000 by Russ Allbery # # This program is free software; you can redistribute it and/or modify it # under the same terms as Perl itself. # # This module is intended to be a replacement for Pod::Text, and attempts to # match its output except for some specific circumstances where other # decisions seemed to produce better output. It uses Pod::Parser and is # designed to be very easy to subclass. ############################################################################ # Modules and declarations ############################################################################ package Pod::PlainText; use strict; require 5.005; use Carp qw(carp croak); use Pod::Select (); use vars qw(@ISA %ESCAPES $VERSION); # We inherit from Pod::Select instead of Pod::Parser so that we can be used # by Pod::Usage. @ISA = qw(Pod::Select); $VERSION = '2.07'; BEGIN { if ($] < 5.006) { require Symbol; import Symbol; } } ############################################################################ # Table of supported E<> escapes ############################################################################ # This table is taken near verbatim from Pod::PlainText in Pod::Parser, # which got it near verbatim from the original Pod::Text. It is therefore # credited to Tom Christiansen, and I'm glad I didn't have to write it. :) %ESCAPES = ( 'amp' => '&', # ampersand 'lt' => '<', # left chevron, less-than 'gt' => '>', # right chevron, greater-than 'quot' => '"', # double quote "Aacute" => "\xC1", # capital A, acute accent "aacute" => "\xE1", # small a, acute accent "Acirc" => "\xC2", # capital A, circumflex accent "acirc" => "\xE2", # small a, circumflex accent "AElig" => "\xC6", # capital AE diphthong (ligature) "aelig" => "\xE6", # small ae diphthong (ligature) "Agrave" => "\xC0", # capital A, grave accent "agrave" => "\xE0", # small a, grave accent "Aring" => "\xC5", # capital A, ring "aring" => "\xE5", # small a, ring "Atilde" => "\xC3", # capital A, tilde "atilde" => "\xE3", # small a, tilde "Auml" => "\xC4", # capital A, dieresis or umlaut mark "auml" => "\xE4", # small a, dieresis or umlaut mark "Ccedil" => "\xC7", # capital C, cedilla "ccedil" => "\xE7", # small c, cedilla "Eacute" => "\xC9", # capital E, acute accent "eacute" => "\xE9", # small e, acute accent "Ecirc" => "\xCA", # capital E, circumflex accent "ecirc" => "\xEA", # small e, circumflex accent "Egrave" => "\xC8", # capital E, grave accent "egrave" => "\xE8", # small e, grave accent "ETH" => "\xD0", # capital Eth, Icelandic "eth" => "\xF0", # small eth, Icelandic "Euml" => "\xCB", # capital E, dieresis or umlaut mark "euml" => "\xEB", # small e, dieresis or umlaut mark "Iacute" => "\xCD", # capital I, acute accent "iacute" => "\xED", # small i, acute accent "Icirc" => "\xCE", # capital I, circumflex accent "icirc" => "\xEE", # small i, circumflex accent "Igrave" => "\xCD", # capital I, grave accent "igrave" => "\xED", # small i, grave accent "Iuml" => "\xCF", # capital I, dieresis or umlaut mark "iuml" => "\xEF", # small i, dieresis or umlaut mark "Ntilde" => "\xD1", # capital N, tilde "ntilde" => "\xF1", # small n, tilde "Oacute" => "\xD3", # capital O, acute accent "oacute" => "\xF3", # small o, acute accent "Ocirc" => "\xD4", # capital O, circumflex accent "ocirc" => "\xF4", # small o, circumflex accent "Ograve" => "\xD2", # capital O, grave accent "ograve" => "\xF2", # small o, grave accent "Oslash" => "\xD8", # capital O, slash "oslash" => "\xF8", # small o, slash "Otilde" => "\xD5", # capital O, tilde "otilde" => "\xF5", # small o, tilde "Ouml" => "\xD6", # capital O, dieresis or umlaut mark "ouml" => "\xF6", # small o, dieresis or umlaut mark "szlig" => "\xDF", # small sharp s, German (sz ligature) "THORN" => "\xDE", # capital THORN, Icelandic "thorn" => "\xFE", # small thorn, Icelandic "Uacute" => "\xDA", # capital U, acute accent "uacute" => "\xFA", # small u, acute accent "Ucirc" => "\xDB", # capital U, circumflex accent "ucirc" => "\xFB", # small u, circumflex accent "Ugrave" => "\xD9", # capital U, grave accent "ugrave" => "\xF9", # small u, grave accent "Uuml" => "\xDC", # capital U, dieresis or umlaut mark "uuml" => "\xFC", # small u, dieresis or umlaut mark "Yacute" => "\xDD", # capital Y, acute accent "yacute" => "\xFD", # small y, acute accent "yuml" => "\xFF", # small y, dieresis or umlaut mark "lchevron" => "\xAB", # left chevron (double less than) "rchevron" => "\xBB", # right chevron (double greater than) ); ############################################################################ # Initialization ############################################################################ # Initialize the object. Must be sure to call our parent initializer. sub initialize { my $self = shift; $$self{alt} = 0 unless defined $$self{alt}; $$self{indent} = 4 unless defined $$self{indent}; $$self{loose} = 0 unless defined $$self{loose}; $$self{sentence} = 0 unless defined $$self{sentence}; $$self{width} = 76 unless defined $$self{width}; $$self{INDENTS} = []; # Stack of indentations. $$self{MARGIN} = $$self{indent}; # Current left margin in spaces. return $self->SUPER::initialize; } ############################################################################ # Core overrides ############################################################################ # Called for each command paragraph. Gets the command, the associated # paragraph, the line number, and a Pod::Paragraph object. Just dispatches # the command to a method named the same as the command. =cut is handled # internally by Pod::Parser. sub command { my $self = shift; my $command = shift; return if $command eq 'pod'; return if ($$self{EXCLUDE} && $command ne 'end'); if (defined $$self{ITEM}) { $self->item ("\n"); local $_ = "\n"; $self->output($_) if($command eq 'back'); } $command = 'cmd_' . $command; return $self->$command (@_); } # Called for a verbatim paragraph. Gets the paragraph, the line number, and # a Pod::Paragraph object. Just output it verbatim, but with tabs converted # to spaces. sub verbatim { my $self = shift; return if $$self{EXCLUDE}; $self->item if defined $$self{ITEM}; local $_ = shift; return if /^\s*$/; s/^(\s*\S+)/(' ' x $$self{MARGIN}) . $1/gme; return $self->output($_); } # Called for a regular text block. Gets the paragraph, the line number, and # a Pod::Paragraph object. Perform interpolation and output the results. sub textblock { my $self = shift; return if $$self{EXCLUDE}; if($$self{VERBATIM}) { $self->output($_[0]); return; } local $_ = shift; my $line = shift; # Perform a little magic to collapse multiple L<> references. This is # here mostly for backwards-compatibility. We'll just rewrite the whole # thing into actual text at this part, bypassing the whole internal # sequence parsing thing. s{ ( L< # A link of the form L. / ( [:\w]+ # The item has to be a simple word... (\(\))? # ...or simple function. ) > ( ,?\s+(and\s+)? # Allow lots of them, conjuncted. L< / ( [:\w]+ (\(\))? ) > )+ ) } { local $_ = $1; s%L]+)>%$1%g; my @items = split /(?:,?\s+(?:and\s+)?)/; my $string = "the "; my $i; for ($i = 0; $i < @items; $i++) { $string .= $items[$i]; $string .= ", " if @items > 2 && $i != $#items; $string .= " and " if ($i == $#items - 1); } $string .= " entries elsewhere in this document"; $string; }gex; # Now actually interpolate and output the paragraph. $_ = $self->interpolate ($_, $line); s/\s*$/\n/s; if (defined $$self{ITEM}) { $self->item ($_ . "\n"); } else { $self->output ($self->reformat ($_ . "\n")); } } # Called for an interior sequence. Gets the command, argument, and a # Pod::InteriorSequence object and is expected to return the resulting text. # Calls code, bold, italic, file, and link to handle those types of # sequences, and handles S<>, E<>, X<>, and Z<> directly. sub interior_sequence { my $self = shift; my $command = shift; local $_ = shift; return '' if ($command eq 'X' || $command eq 'Z'); # Expand escapes into the actual character now, carping if invalid. if ($command eq 'E') { return $ESCAPES{$_} if defined $ESCAPES{$_}; carp "Unknown escape: E<$_>"; return "E<$_>"; } # For all the other sequences, empty content produces no output. return if $_ eq ''; # For S<>, compress all internal whitespace and then map spaces to \01. # When we output the text, we'll map this back. if ($command eq 'S') { s/\s{2,}/ /g; tr/ /\01/; return $_; } # Anything else needs to get dispatched to another method. if ($command eq 'B') { return $self->seq_b ($_) } elsif ($command eq 'C') { return $self->seq_c ($_) } elsif ($command eq 'F') { return $self->seq_f ($_) } elsif ($command eq 'I') { return $self->seq_i ($_) } elsif ($command eq 'L') { return $self->seq_l ($_) } else { carp "Unknown sequence $command<$_>" } } # Called for each paragraph that's actually part of the POD. We take # advantage of this opportunity to untabify the input. sub preprocess_paragraph { my $self = shift; local $_ = shift; 1 while s/^(.*?)(\t+)/$1 . ' ' x (length ($2) * 8 - length ($1) % 8)/me; return $_; } ############################################################################ # Command paragraphs ############################################################################ # All command paragraphs take the paragraph and the line number. # First level heading. sub cmd_head1 { my $self = shift; local $_ = shift; s/\s+$//s; $_ = $self->interpolate ($_, shift); if ($$self{alt}) { $self->output ("\n==== $_ ====\n\n"); } else { $_ .= "\n" if $$self{loose}; $self->output ($_ . "\n"); } } # Second level heading. sub cmd_head2 { my $self = shift; local $_ = shift; s/\s+$//s; $_ = $self->interpolate ($_, shift); if ($$self{alt}) { $self->output ("\n== $_ ==\n\n"); } else { $_ .= "\n" if $$self{loose}; $self->output (' ' x ($$self{indent} / 2) . $_ . "\n"); } } # third level heading - not strictly perlpodspec compliant sub cmd_head3 { my $self = shift; local $_ = shift; s/\s+$//s; $_ = $self->interpolate ($_, shift); if ($$self{alt}) { $self->output ("\n= $_ =\n"); } else { $_ .= "\n" if $$self{loose}; $self->output (' ' x ($$self{indent}) . $_ . "\n"); } } # fourth level heading - not strictly perlpodspec compliant # just like head3 *cmd_head4 = \&cmd_head3; # Start a list. sub cmd_over { my $self = shift; local $_ = shift; unless (/^[-+]?\d+\s+$/) { $_ = $$self{indent} } push (@{ $$self{INDENTS} }, $$self{MARGIN}); $$self{MARGIN} += ($_ + 0); } # End a list. sub cmd_back { my $self = shift; $$self{MARGIN} = pop @{ $$self{INDENTS} }; unless (defined $$self{MARGIN}) { carp 'Unmatched =back'; $$self{MARGIN} = $$self{indent}; } } # An individual list item. sub cmd_item { my $self = shift; if (defined $$self{ITEM}) { $self->item } local $_ = shift; s/\s+$//s; $$self{ITEM} = $self->interpolate ($_); } # Begin a block for a particular translator. Setting VERBATIM triggers # special handling in textblock(). sub cmd_begin { my $self = shift; local $_ = shift; my ($kind) = /^(\S+)/ or return; if ($kind eq 'text') { $$self{VERBATIM} = 1; } else { $$self{EXCLUDE} = 1; } } # End a block for a particular translator. We assume that all =begin/=end # pairs are properly closed. sub cmd_end { my $self = shift; $$self{EXCLUDE} = 0; $$self{VERBATIM} = 0; } # One paragraph for a particular translator. Ignore it unless it's intended # for text, in which case we treat it as a verbatim text block. sub cmd_for { my $self = shift; local $_ = shift; my $line = shift; return unless s/^text\b[ \t]*\r?\n?//; $self->verbatim ($_, $line); } # just a dummy method for the time being sub cmd_encoding { return; } ############################################################################ # Interior sequences ############################################################################ # The simple formatting ones. These are here mostly so that subclasses can # override them and do more complicated things. sub seq_b { return $_[0]{alt} ? "``$_[1]''" : $_[1] } sub seq_c { return $_[0]{alt} ? "``$_[1]''" : "`$_[1]'" } sub seq_f { return $_[0]{alt} ? "\"$_[1]\"" : $_[1] } sub seq_i { return '*' . $_[1] . '*' } # The complicated one. Handle links. Since this is plain text, we can't # actually make any real links, so this is all to figure out what text we # print out. sub seq_l { my $self = shift; local $_ = shift; # Smash whitespace in case we were split across multiple lines. s/\s+/ /g; # If we were given any explicit text, just output it. if (/^([^|]+)\|/) { return $1 } # Okay, leading and trailing whitespace isn't important; get rid of it. s/^\s+//; s/\s+$//; # Default to using the whole content of the link entry as a section # name. Note that L forces a manpage interpretation, as does # something looking like L. The latter is an # enhancement over the original Pod::Text. my ($manpage, $section) = ('', $_); if (/^(?:https?|ftp|news):/) { # a URL return $_; } elsif (/^"\s*(.*?)\s*"$/) { $section = '"' . $1 . '"'; } elsif (m/^[-:.\w]+(?:\(\S+\))?$/) { ($manpage, $section) = ($_, ''); } elsif (m{/}) { ($manpage, $section) = split (/\s*\/\s*/, $_, 2); } my $text = ''; # Now build the actual output text. if (!length $section) { $text = "the $manpage manpage" if length $manpage; } elsif ($section =~ /^[:\w]+(?:\(\))?/) { $text .= 'the ' . $section . ' entry'; $text .= (length $manpage) ? " in the $manpage manpage" : ' elsewhere in this document'; } else { $section =~ s/^\"\s*//; $section =~ s/\s*\"$//; $text .= 'the section on "' . $section . '"'; $text .= " in the $manpage manpage" if length $manpage; } return $text; } ############################################################################ # List handling ############################################################################ # This method is called whenever an =item command is complete (in other # words, we've seen its associated paragraph or know for certain that it # doesn't have one). It gets the paragraph associated with the item as an # argument. If that argument is empty, just output the item tag; if it # contains a newline, output the item tag followed by the newline. # Otherwise, see if there's enough room for us to output the item tag in the # margin of the text or if we have to put it on a separate line. sub item { my $self = shift; local $_ = shift; my $tag = $$self{ITEM}; unless (defined $tag) { carp 'item called without tag'; return; } undef $$self{ITEM}; my $indent = $$self{INDENTS}[-1]; unless (defined $indent) { $indent = $$self{indent} } my $space = ' ' x $indent; $space =~ s/^ /:/ if $$self{alt}; if (!$_ || /^\s+$/ || ($$self{MARGIN} - $indent < length ($tag) + 1)) { my $margin = $$self{MARGIN}; $$self{MARGIN} = $indent; my $output = $self->reformat ($tag); $output =~ s/[\r\n]*$/\n/; $self->output ($output); $$self{MARGIN} = $margin; $self->output ($self->reformat ($_)) if /\S/; } else { $_ = $self->reformat ($_); s/^ /:/ if ($$self{alt} && $indent > 0); my $tagspace = ' ' x length $tag; s/^($space)$tagspace/$1$tag/ or carp 'Bizarre space in item'; $self->output ($_); } } ############################################################################ # Output formatting ############################################################################ # Wrap a line, indenting by the current left margin. We can't use # Text::Wrap because it plays games with tabs. We can't use formline, even # though we'd really like to, because it screws up non-printing characters. # So we have to do the wrapping ourselves. sub wrap { my $self = shift; local $_ = shift; my $output = ''; my $spaces = ' ' x $$self{MARGIN}; my $width = $$self{width} - $$self{MARGIN}; while (length > $width) { if (s/^([^\r\n]{0,$width})\s+// || s/^([^\r\n]{$width})//) { $output .= $spaces . $1 . "\n"; } else { last; } } $output .= $spaces . $_; $output =~ s/\s+$/\n\n/; return $output; } # Reformat a paragraph of text for the current margin. Takes the text to # reformat and returns the formatted text. sub reformat { my $self = shift; local $_ = shift; # If we're trying to preserve two spaces after sentences, do some # munging to support that. Otherwise, smash all repeated whitespace. if ($$self{sentence}) { s/ +$//mg; s/\.\r?\n/. \n/g; s/[\r\n]+/ /g; s/ +/ /g; } else { s/\s+/ /g; } return $self->wrap($_); } # Output text to the output device. sub output { $_[1] =~ tr/\01/ /; print { $_[0]->output_handle } $_[1] } ############################################################################ # Backwards compatibility ############################################################################ # The old Pod::Text module did everything in a pod2text() function. This # tries to provide the same interface for legacy applications. sub pod2text { my @args; # This is really ugly; I hate doing option parsing in the middle of a # module. But the old Pod::Text module supported passing flags to its # entry function, so handle -a and -. while ($_[0] =~ /^-/) { my $flag = shift; if ($flag eq '-a') { push (@args, alt => 1) } elsif ($flag =~ /^-(\d+)$/) { push (@args, width => $1) } else { unshift (@_, $flag); last; } } # Now that we know what arguments we're using, create the parser. my $parser = Pod::PlainText->new (@args); # If two arguments were given, the second argument is going to be a file # handle. That means we want to call parse_from_filehandle(), which # means we need to turn the first argument into a file handle. Magic # open will handle the <&STDIN case automagically. if (defined $_[1]) { my $infh; if ($] < 5.006) { $infh = gensym(); } unless (open ($infh, $_[0])) { croak ("Can't open $_[0] for reading: $!\n"); } $_[0] = $infh; return $parser->parse_from_filehandle (@_); } else { return $parser->parse_from_file (@_); } } ############################################################################ # Module return value and documentation ############################################################################ 1; __END__ # Pod::ParseLink -- Parse an L<> formatting code in POD text. # # Copyright 2001, 2008, 2009, 2014 by Russ Allbery # # This program is free software; you may redistribute it and/or modify it # under the same terms as Perl itself. # # This module implements parsing of the text of an L<> formatting code as # defined in perlpodspec. It should be suitable for any POD formatter. It # exports only one function, parselink(), which returns the five-item parse # defined in perlpodspec. # # Perl core hackers, please note that this module is also separately # maintained outside of the Perl core as part of the podlators. Please send # me any patches at the address above in addition to sending them to the # standard Perl mailing lists. ############################################################################## # Modules and declarations ############################################################################## package Pod::ParseLink; use 5.006; use strict; use warnings; use vars qw(@EXPORT @ISA $VERSION); use Exporter; @ISA = qw(Exporter); @EXPORT = qw(parselink); $VERSION = '4.10'; ############################################################################## # Implementation ############################################################################## # Parse the name and section portion of a link into a name and section. sub _parse_section { my ($link) = @_; $link =~ s/^\s+//; $link =~ s/\s+$//; # If the whole link is enclosed in quotes, interpret it all as a section # even if it contains a slash. return (undef, $1) if ($link =~ /^"\s*(.*?)\s*"$/); # Split into page and section on slash, and then clean up quoting in the # section. If there is no section and the name contains spaces, also # guess that it's an old section link. my ($page, $section) = split (/\s*\/\s*/, $link, 2); $section =~ s/^"\s*(.*?)\s*"$/$1/ if $section; if ($page && $page =~ / / && !defined ($section)) { $section = $page; $page = undef; } else { $page = undef unless $page; $section = undef unless $section; } return ($page, $section); } # Infer link text from the page and section. sub _infer_text { my ($page, $section) = @_; my $inferred; if ($page && !$section) { $inferred = $page; } elsif (!$page && $section) { $inferred = '"' . $section . '"'; } elsif ($page && $section) { $inferred = '"' . $section . '" in ' . $page; } return $inferred; } # Given the contents of an L<> formatting code, parse it into the link text, # the possibly inferred link text, the name or URL, the section, and the type # of link (pod, man, or url). sub parselink { my ($link) = @_; $link =~ s/\s+/ /g; my $text; if ($link =~ /\|/) { ($text, $link) = split (/\|/, $link, 2); } if ($link =~ /\A\w+:[^:\s]\S*\Z/) { my $inferred; if (defined ($text) && length ($text) > 0) { return ($text, $text, $link, undef, 'url'); } else { return ($text, $link, $link, undef, 'url'); } } else { my ($name, $section) = _parse_section ($link); my $inferred; if (defined ($text) && length ($text) > 0) { $inferred = $text; } else { $inferred = _infer_text ($name, $section); } my $type = ($name && $name =~ /\(\S*\)/) ? 'man' : 'pod'; return ($text, $inferred, $name, $section, $type); } } ############################################################################## # Module return value and documentation ############################################################################## # Ensure we evaluate to true. 1; __END__ # Convert POD data to formatted text. # # This module converts POD to formatted text. It replaces the old Pod::Text # module that came with versions of Perl prior to 5.6.0 and attempts to match # its output except for some specific circumstances where other decisions # seemed to produce better output. It uses Pod::Parser and is designed to be # very easy to subclass. # # Perl core hackers, please note that this module is also separately # maintained outside of the Perl core as part of the podlators. Please send # me any patches at the address above in addition to sending them to the # standard Perl mailing lists. # # Copyright 1999, 2000, 2001, 2002, 2004, 2006, 2008, 2009, 2012, 2013, 2014, # 2015, 2016 Russ Allbery # # This program is free software; you may redistribute it and/or modify it # under the same terms as Perl itself. ############################################################################## # Modules and declarations ############################################################################## package Pod::Text; use 5.006; use strict; use warnings; use vars qw(@ISA @EXPORT %ESCAPES $VERSION); use Carp qw(carp croak); use Encode qw(encode); use Exporter (); use Pod::Simple (); @ISA = qw(Pod::Simple Exporter); # We have to export pod2text for backward compatibility. @EXPORT = qw(pod2text); $VERSION = '4.10'; # Ensure that $Pod::Simple::nbsp and $Pod::Simple::shy are available. Code # taken from Pod::Simple 3.32, but was only added in 3.30. my ($NBSP, $SHY); if ($Pod::Simple::VERSION ge 3.30) { $NBSP = $Pod::Simple::nbsp; $SHY = $Pod::Simple::shy; } else { if ($] ge 5.007_003) { $NBSP = chr utf8::unicode_to_native(0xA0); $SHY = chr utf8::unicode_to_native(0xAD); } elsif (Pod::Simple::ASCII) { $NBSP = "\xA0"; $SHY = "\xAD"; } else { $NBSP = "\x41"; $SHY = "\xCA"; } } ############################################################################## # Initialization ############################################################################## # This function handles code blocks. It's registered as a callback to # Pod::Simple and therefore doesn't work as a regular method call, but all it # does is call output_code with the line. sub handle_code { my ($line, $number, $parser) = @_; $parser->output_code ($line . "\n"); } # Initialize the object and set various Pod::Simple options that we need. # Here, we also process any additional options passed to the constructor or # set up defaults if none were given. Note that all internal object keys are # in all-caps, reserving all lower-case object keys for Pod::Simple and user # arguments. sub new { my $class = shift; my $self = $class->SUPER::new; # Tell Pod::Simple to handle S<> by automatically inserting  . $self->nbsp_for_S (1); # Tell Pod::Simple to keep whitespace whenever possible. if ($self->can ('preserve_whitespace')) { $self->preserve_whitespace (1); } else { $self->fullstop_space_harden (1); } # The =for and =begin targets that we accept. $self->accept_targets (qw/text TEXT/); # Ensure that contiguous blocks of code are merged together. Otherwise, # some of the guesswork heuristics don't work right. $self->merge_text (1); # Pod::Simple doesn't do anything useful with our arguments, but we want # to put them in our object as hash keys and values. This could cause # problems if we ever clash with Pod::Simple's own internal class # variables. my %opts = @_; my @opts = map { ("opt_$_", $opts{$_}) } keys %opts; %$self = (%$self, @opts); # Send errors to stderr if requested. if ($$self{opt_stderr} and not $$self{opt_errors}) { $$self{opt_errors} = 'stderr'; } delete $$self{opt_stderr}; # Validate the errors parameter and act on it. if (not defined $$self{opt_errors}) { $$self{opt_errors} = 'pod'; } if ($$self{opt_errors} eq 'stderr' || $$self{opt_errors} eq 'die') { $self->no_errata_section (1); $self->complain_stderr (1); if ($$self{opt_errors} eq 'die') { $$self{complain_die} = 1; } } elsif ($$self{opt_errors} eq 'pod') { $self->no_errata_section (0); $self->complain_stderr (0); } elsif ($$self{opt_errors} eq 'none') { $self->no_whining (1); } else { croak (qq(Invalid errors setting: "$$self{errors}")); } delete $$self{errors}; # Initialize various things from our parameters. $$self{opt_alt} = 0 unless defined $$self{opt_alt}; $$self{opt_indent} = 4 unless defined $$self{opt_indent}; $$self{opt_margin} = 0 unless defined $$self{opt_margin}; $$self{opt_loose} = 0 unless defined $$self{opt_loose}; $$self{opt_sentence} = 0 unless defined $$self{opt_sentence}; $$self{opt_width} = 76 unless defined $$self{opt_width}; # Figure out what quotes we'll be using for C<> text. $$self{opt_quotes} ||= '"'; if ($$self{opt_quotes} eq 'none') { $$self{LQUOTE} = $$self{RQUOTE} = ''; } elsif (length ($$self{opt_quotes}) == 1) { $$self{LQUOTE} = $$self{RQUOTE} = $$self{opt_quotes}; } elsif (length ($$self{opt_quotes}) % 2 == 0) { my $length = length ($$self{opt_quotes}) / 2; $$self{LQUOTE} = substr ($$self{opt_quotes}, 0, $length); $$self{RQUOTE} = substr ($$self{opt_quotes}, $length); } else { croak qq(Invalid quote specification "$$self{opt_quotes}"); } # If requested, do something with the non-POD text. $self->code_handler (\&handle_code) if $$self{opt_code}; # Return the created object. return $self; } ############################################################################## # Core parsing ############################################################################## # This is the glue that connects the code below with Pod::Simple itself. The # goal is to convert the event stream coming from the POD parser into method # calls to handlers once the complete content of a tag has been seen. Each # paragraph or POD command will have textual content associated with it, and # as soon as all of a paragraph or POD command has been seen, that content # will be passed in to the corresponding method for handling that type of # object. The exceptions are handlers for lists, which have opening tag # handlers and closing tag handlers that will be called right away. # # The internal hash key PENDING is used to store the contents of a tag until # all of it has been seen. It holds a stack of open tags, each one # represented by a tuple of the attributes hash for the tag and the contents # of the tag. # Add a block of text to the contents of the current node, formatting it # according to the current formatting instructions as we do. sub _handle_text { my ($self, $text) = @_; my $tag = $$self{PENDING}[-1]; $$tag[1] .= $text; } # Given an element name, get the corresponding method name. sub method_for_element { my ($self, $element) = @_; $element =~ tr/-/_/; $element =~ tr/A-Z/a-z/; $element =~ tr/_a-z0-9//cd; return $element; } # Handle the start of a new element. If cmd_element is defined, assume that # we need to collect the entire tree for this element before passing it to the # element method, and create a new tree into which we'll collect blocks of # text and nested elements. Otherwise, if start_element is defined, call it. sub _handle_element_start { my ($self, $element, $attrs) = @_; my $method = $self->method_for_element ($element); # If we have a command handler, we need to accumulate the contents of the # tag before calling it. if ($self->can ("cmd_$method")) { push (@{ $$self{PENDING} }, [ $attrs, '' ]); } elsif ($self->can ("start_$method")) { my $method = 'start_' . $method; $self->$method ($attrs, ''); } } # Handle the end of an element. If we had a cmd_ method for this element, # this is where we pass along the text that we've accumulated. Otherwise, if # we have an end_ method for the element, call that. sub _handle_element_end { my ($self, $element) = @_; my $method = $self->method_for_element ($element); # If we have a command handler, pull off the pending text and pass it to # the handler along with the saved attribute hash. if ($self->can ("cmd_$method")) { my $tag = pop @{ $$self{PENDING} }; my $method = 'cmd_' . $method; my $text = $self->$method (@$tag); if (defined $text) { if (@{ $$self{PENDING} } > 1) { $$self{PENDING}[-1][1] .= $text; } else { $self->output ($text); } } } elsif ($self->can ("end_$method")) { my $method = 'end_' . $method; $self->$method (); } } ############################################################################## # Output formatting ############################################################################## # Wrap a line, indenting by the current left margin. We can't use Text::Wrap # because it plays games with tabs. We can't use formline, even though we'd # really like to, because it screws up non-printing characters. So we have to # do the wrapping ourselves. sub wrap { my $self = shift; local $_ = shift; my $output = ''; my $spaces = ' ' x $$self{MARGIN}; my $width = $$self{opt_width} - $$self{MARGIN}; while (length > $width) { if (s/^([^\n]{0,$width})\s+// || s/^([^\n]{$width})//) { $output .= $spaces . $1 . "\n"; } else { last; } } $output .= $spaces . $_; $output =~ s/\s+$/\n\n/; return $output; } # Reformat a paragraph of text for the current margin. Takes the text to # reformat and returns the formatted text. sub reformat { my $self = shift; local $_ = shift; # If we're trying to preserve two spaces after sentences, do some munging # to support that. Otherwise, smash all repeated whitespace. if ($$self{opt_sentence}) { s/ +$//mg; s/\.\n/. \n/g; s/\n/ /g; s/ +/ /g; } else { s/\s+/ /g; } return $self->wrap ($_); } # Output text to the output device. Replace non-breaking spaces with spaces # and soft hyphens with nothing, and then try to fix the output encoding if # necessary to match the input encoding unless UTF-8 output is forced. This # preserves the traditional pass-through behavior of Pod::Text. sub output { my ($self, @text) = @_; my $text = join ('', @text); if ($NBSP) { $text =~ s/$NBSP/ /g; } if ($SHY) { $text =~ s/$SHY//g; } unless ($$self{opt_utf8}) { my $encoding = $$self{encoding} || ''; if ($encoding && $encoding ne $$self{ENCODING}) { $$self{ENCODING} = $encoding; eval { binmode ($$self{output_fh}, ":encoding($encoding)") }; } } if ($$self{ENCODE}) { print { $$self{output_fh} } encode ('UTF-8', $text); } else { print { $$self{output_fh} } $text; } } # Output a block of code (something that isn't part of the POD text). Called # by preprocess_paragraph only if we were given the code option. Exists here # only so that it can be overridden by subclasses. sub output_code { $_[0]->output ($_[1]) } ############################################################################## # Document initialization ############################################################################## # Set up various things that have to be initialized on a per-document basis. sub start_document { my ($self, $attrs) = @_; if ($$attrs{contentless} && !$$self{ALWAYS_EMIT_SOMETHING}) { $$self{CONTENTLESS} = 1; } else { delete $$self{CONTENTLESS}; } my $margin = $$self{opt_indent} + $$self{opt_margin}; # Initialize a few per-document variables. $$self{INDENTS} = []; # Stack of indentations. $$self{MARGIN} = $margin; # Default left margin. $$self{PENDING} = [[]]; # Pending output. # We have to redo encoding handling for each document. $$self{ENCODING} = ''; # When UTF-8 output is set, check whether our output file handle already # has a PerlIO encoding layer set. If it does not, we'll need to encode # our output before printing it (handled in the output() sub). Wrap the # check in an eval to handle versions of Perl without PerlIO. $$self{ENCODE} = 0; if ($$self{opt_utf8}) { $$self{ENCODE} = 1; eval { my @options = (output => 1, details => 1); my $flag = (PerlIO::get_layers ($$self{output_fh}, @options))[-1]; if ($flag & PerlIO::F_UTF8 ()) { $$self{ENCODE} = 0; $$self{ENCODING} = 'UTF-8'; } }; } return ''; } # Handle the end of the document. The only thing we do is handle dying on POD # errors, since Pod::Parser currently doesn't. sub end_document { my ($self) = @_; if ($$self{complain_die} && $self->errors_seen) { croak ("POD document had syntax errors"); } } ############################################################################## # Text blocks ############################################################################## # Intended for subclasses to override, this method returns text with any # non-printing formatting codes stripped out so that length() correctly # returns the length of the text. For basic Pod::Text, it does nothing. sub strip_format { my ($self, $string) = @_; return $string; } # This method is called whenever an =item command is complete (in other words, # we've seen its associated paragraph or know for certain that it doesn't have # one). It gets the paragraph associated with the item as an argument. If # that argument is empty, just output the item tag; if it contains a newline, # output the item tag followed by the newline. Otherwise, see if there's # enough room for us to output the item tag in the margin of the text or if we # have to put it on a separate line. sub item { my ($self, $text) = @_; my $tag = $$self{ITEM}; unless (defined $tag) { carp "Item called without tag"; return; } undef $$self{ITEM}; # Calculate the indentation and margin. $fits is set to true if the tag # will fit into the margin of the paragraph given our indentation level. my $indent = $$self{INDENTS}[-1]; $indent = $$self{opt_indent} unless defined $indent; my $margin = ' ' x $$self{opt_margin}; my $tag_length = length ($self->strip_format ($tag)); my $fits = ($$self{MARGIN} - $indent >= $tag_length + 1); # If the tag doesn't fit, or if we have no associated text, print out the # tag separately. Otherwise, put the tag in the margin of the paragraph. if (!$text || $text =~ /^\s+$/ || !$fits) { my $realindent = $$self{MARGIN}; $$self{MARGIN} = $indent; my $output = $self->reformat ($tag); $output =~ s/^$margin /$margin:/ if ($$self{opt_alt} && $indent > 0); $output =~ s/\n*$/\n/; # If the text is just whitespace, we have an empty item paragraph; # this can result from =over/=item/=back without any intermixed # paragraphs. Insert some whitespace to keep the =item from merging # into the next paragraph. $output .= "\n" if $text && $text =~ /^\s*$/; $self->output ($output); $$self{MARGIN} = $realindent; $self->output ($self->reformat ($text)) if ($text && $text =~ /\S/); } else { my $space = ' ' x $indent; $space =~ s/^$margin /$margin:/ if $$self{opt_alt}; $text = $self->reformat ($text); $text =~ s/^$margin /$margin:/ if ($$self{opt_alt} && $indent > 0); my $tagspace = ' ' x $tag_length; $text =~ s/^($space)$tagspace/$1$tag/ or warn "Bizarre space in item"; $self->output ($text); } } # Handle a basic block of text. The only tricky thing here is that if there # is a pending item tag, we need to format this as an item paragraph. sub cmd_para { my ($self, $attrs, $text) = @_; $text =~ s/\s+$/\n/; if (defined $$self{ITEM}) { $self->item ($text . "\n"); } else { $self->output ($self->reformat ($text . "\n")); } return ''; } # Handle a verbatim paragraph. Just print it out, but indent it according to # our margin. sub cmd_verbatim { my ($self, $attrs, $text) = @_; $self->item if defined $$self{ITEM}; return if $text =~ /^\s*$/; $text =~ s/^(\n*)([ \t]*\S+)/$1 . (' ' x $$self{MARGIN}) . $2/gme; $text =~ s/\s*$/\n\n/; $self->output ($text); return ''; } # Handle literal text (produced by =for and similar constructs). Just output # it with the minimum of changes. sub cmd_data { my ($self, $attrs, $text) = @_; $text =~ s/^\n+//; $text =~ s/\n{0,2}$/\n/; $self->output ($text); return ''; } ############################################################################## # Headings ############################################################################## # The common code for handling all headers. Takes the header text, the # indentation, and the surrounding marker for the alt formatting method. sub heading { my ($self, $text, $indent, $marker) = @_; $self->item ("\n\n") if defined $$self{ITEM}; $text =~ s/\s+$//; if ($$self{opt_alt}) { my $closemark = reverse (split (//, $marker)); my $margin = ' ' x $$self{opt_margin}; $self->output ("\n" . "$margin$marker $text $closemark" . "\n\n"); } else { $text .= "\n" if $$self{opt_loose}; my $margin = ' ' x ($$self{opt_margin} + $indent); $self->output ($margin . $text . "\n"); } return ''; } # First level heading. sub cmd_head1 { my ($self, $attrs, $text) = @_; $self->heading ($text, 0, '===='); } # Second level heading. sub cmd_head2 { my ($self, $attrs, $text) = @_; $self->heading ($text, $$self{opt_indent} / 2, '== '); } # Third level heading. sub cmd_head3 { my ($self, $attrs, $text) = @_; $self->heading ($text, $$self{opt_indent} * 2 / 3 + 0.5, '= '); } # Fourth level heading. sub cmd_head4 { my ($self, $attrs, $text) = @_; $self->heading ($text, $$self{opt_indent} * 3 / 4 + 0.5, '- '); } ############################################################################## # List handling ############################################################################## # Handle the beginning of an =over block. Takes the type of the block as the # first argument, and then the attr hash. This is called by the handlers for # the four different types of lists (bullet, number, text, and block). sub over_common_start { my ($self, $attrs) = @_; $self->item ("\n\n") if defined $$self{ITEM}; # Find the indentation level. my $indent = $$attrs{indent}; unless (defined ($indent) && $indent =~ /^\s*[-+]?\d{1,4}\s*$/) { $indent = $$self{opt_indent}; } # Add this to our stack of indents and increase our current margin. push (@{ $$self{INDENTS} }, $$self{MARGIN}); $$self{MARGIN} += ($indent + 0); return ''; } # End an =over block. Takes no options other than the class pointer. Output # any pending items and then pop one level of indentation. sub over_common_end { my ($self) = @_; $self->item ("\n\n") if defined $$self{ITEM}; $$self{MARGIN} = pop @{ $$self{INDENTS} }; return ''; } # Dispatch the start and end calls as appropriate. sub start_over_bullet { $_[0]->over_common_start ($_[1]) } sub start_over_number { $_[0]->over_common_start ($_[1]) } sub start_over_text { $_[0]->over_common_start ($_[1]) } sub start_over_block { $_[0]->over_common_start ($_[1]) } sub end_over_bullet { $_[0]->over_common_end } sub end_over_number { $_[0]->over_common_end } sub end_over_text { $_[0]->over_common_end } sub end_over_block { $_[0]->over_common_end } # The common handler for all item commands. Takes the type of the item, the # attributes, and then the text of the item. sub item_common { my ($self, $type, $attrs, $text) = @_; $self->item if defined $$self{ITEM}; # Clean up the text. We want to end up with two variables, one ($text) # which contains any body text after taking out the item portion, and # another ($item) which contains the actual item text. Note the use of # the internal Pod::Simple attribute here; that's a potential land mine. $text =~ s/\s+$//; my ($item, $index); if ($type eq 'bullet') { $item = '*'; } elsif ($type eq 'number') { $item = $$attrs{'~orig_content'}; } else { $item = $text; $item =~ s/\s*\n\s*/ /g; $text = ''; } $$self{ITEM} = $item; # If body text for this item was included, go ahead and output that now. if ($text) { $text =~ s/\s*$/\n/; $self->item ($text); } return ''; } # Dispatch the item commands to the appropriate place. sub cmd_item_bullet { my $self = shift; $self->item_common ('bullet', @_) } sub cmd_item_number { my $self = shift; $self->item_common ('number', @_) } sub cmd_item_text { my $self = shift; $self->item_common ('text', @_) } sub cmd_item_block { my $self = shift; $self->item_common ('block', @_) } ############################################################################## # Formatting codes ############################################################################## # The simple ones. sub cmd_b { return $_[0]{alt} ? "``$_[2]''" : $_[2] } sub cmd_f { return $_[0]{alt} ? "\"$_[2]\"" : $_[2] } sub cmd_i { return '*' . $_[2] . '*' } sub cmd_x { return '' } # Apply a whole bunch of messy heuristics to not quote things that don't # benefit from being quoted. These originally come from Barrie Slaymaker and # largely duplicate code in Pod::Man. sub cmd_c { my ($self, $attrs, $text) = @_; # A regex that matches the portion of a variable reference that's the # array or hash index, separated out just because we want to use it in # several places in the following regex. my $index = '(?: \[.*\] | \{.*\} )?'; # Check for things that we don't want to quote, and if we find any of # them, return the string with just a font change and no quoting. $text =~ m{ ^\s* (?: ( [\'\`\"] ) .* \1 # already quoted | \` .* \' # `quoted' | \$+ [\#^]? \S $index # special ($^Foo, $") | [\$\@%&*]+ \#? [:\'\w]+ $index # plain var or func | [\$\@%&*]* [:\'\w]+ (?: -> )? \(\s*[^\s,]\s*\) # 0/1-arg func call | [+-]? ( \d[\d.]* | \.\d+ ) (?: [eE][+-]?\d+ )? # a number | 0x [a-fA-F\d]+ # a hex constant ) \s*\z }xo && return $text; # If we didn't return, go ahead and quote the text. return $$self{opt_alt} ? "``$text''" : "$$self{LQUOTE}$text$$self{RQUOTE}"; } # Links reduce to the text that we're given, wrapped in angle brackets if it's # a URL. sub cmd_l { my ($self, $attrs, $text) = @_; if ($$attrs{type} eq 'url') { if (not defined($$attrs{to}) or $$attrs{to} eq $text) { return "<$text>"; } elsif ($$self{opt_nourls}) { return $text; } else { return "$text <$$attrs{to}>"; } } else { return $text; } } ############################################################################## # Backwards compatibility ############################################################################## # The old Pod::Text module did everything in a pod2text() function. This # tries to provide the same interface for legacy applications. sub pod2text { my @args; # This is really ugly; I hate doing option parsing in the middle of a # module. But the old Pod::Text module supported passing flags to its # entry function, so handle -a and -. while ($_[0] =~ /^-/) { my $flag = shift; if ($flag eq '-a') { push (@args, alt => 1) } elsif ($flag =~ /^-(\d+)$/) { push (@args, width => $1) } else { unshift (@_, $flag); last; } } # Now that we know what arguments we're using, create the parser. my $parser = Pod::Text->new (@args); # If two arguments were given, the second argument is going to be a file # handle. That means we want to call parse_from_filehandle(), which means # we need to turn the first argument into a file handle. Magic open will # handle the <&STDIN case automagically. if (defined $_[1]) { my @fhs = @_; local *IN; unless (open (IN, $fhs[0])) { croak ("Can't open $fhs[0] for reading: $!\n"); return; } $fhs[0] = \*IN; $parser->output_fh ($fhs[1]); my $retval = $parser->parse_file ($fhs[0]); my $fh = $parser->output_fh (); close $fh; return $retval; } else { $parser->output_fh (\*STDOUT); return $parser->parse_file (@_); } } # Reset the underlying Pod::Simple object between calls to parse_from_file so # that the same object can be reused to convert multiple pages. sub parse_from_file { my $self = shift; $self->reinit; # Fake the old cutting option to Pod::Parser. This fiddles with internal # Pod::Simple state and is quite ugly; we need a better approach. if (ref ($_[0]) eq 'HASH') { my $opts = shift @_; if (defined ($$opts{-cutting}) && !$$opts{-cutting}) { $$self{in_pod} = 1; $$self{last_was_blank} = 1; } } # Do the work. my $retval = $self->Pod::Simple::parse_from_file (@_); # Flush output, since Pod::Simple doesn't do this. Ideally we should also # close the file descriptor if we had to open one, but we can't easily # figure this out. my $fh = $self->output_fh (); my $oldfh = select $fh; my $oldflush = $|; $| = 1; print $fh ''; $| = $oldflush; select $oldfh; return $retval; } # Pod::Simple failed to provide this backward compatibility function, so # implement it ourselves. File handles are one of the inputs that # parse_from_file supports. sub parse_from_filehandle { my $self = shift; $self->parse_from_file (@_); } # Pod::Simple's parse_file doesn't set output_fh. Wrap the call and do so # ourself unless it was already set by the caller, since our documentation has # always said that this should work. sub parse_file { my ($self, $in) = @_; unless (defined $$self{output_fh}) { $self->output_fh (\*STDOUT); } return $self->SUPER::parse_file ($in); } # Do the same for parse_lines, just to be polite. Pod::Simple's man page # implies that the caller is responsible for setting this, but I don't see any # reason not to set a default. sub parse_lines { my ($self, @lines) = @_; unless (defined $$self{output_fh}) { $self->output_fh (\*STDOUT); } return $self->SUPER::parse_lines (@lines); } # Likewise for parse_string_document. sub parse_string_document { my ($self, $doc) = @_; unless (defined $$self{output_fh}) { $self->output_fh (\*STDOUT); } return $self->SUPER::parse_string_document ($doc); } ############################################################################## # Module return value and documentation ############################################################################## 1; __END__ package Pod::Escapes; use strict; use warnings; use 5.006; use vars qw( %Code2USASCII %Name2character %Name2character_number %Latin1Code_to_fallback %Latin1Char_to_fallback $FAR_CHAR $FAR_CHAR_NUMBER $NOT_ASCII @ISA $VERSION @EXPORT_OK %EXPORT_TAGS ); require Exporter; @ISA = ('Exporter'); $VERSION = '1.07'; @EXPORT_OK = qw( %Code2USASCII %Name2character %Name2character_number %Latin1Code_to_fallback %Latin1Char_to_fallback e2char e2charnum ); %EXPORT_TAGS = ('ALL' => \@EXPORT_OK); #========================================================================== $FAR_CHAR = "?" unless defined $FAR_CHAR; $FAR_CHAR_NUMBER = ord($FAR_CHAR) unless defined $FAR_CHAR_NUMBER; $NOT_ASCII = 'A' ne chr(65) unless defined $NOT_ASCII; #-------------------------------------------------------------------------- sub e2char { my $in = $_[0]; return undef unless defined $in and length $in; # Convert to decimal: if($in =~ m/^(0[0-7]*)$/s ) { $in = oct $in; } elsif($in =~ m/^0?x([0-9a-fA-F]+)$/s ) { $in = hex $1; } # else it's decimal, or named if($in =~ m/^\d+$/s) { if($] < 5.007 and $in > 255) { # can't be trusted with Unicode return $FAR_CHAR; } elsif ($] >= 5.007003) { return chr(utf8::unicode_to_native($in)); } elsif ($NOT_ASCII) { return $Code2USASCII{$in} # so "65" => "A" everywhere || $Latin1Code_to_fallback{$in} # Fallback. || $FAR_CHAR; # Fall further back } else { return chr($in); } } else { return $Name2character{$in}; # returns undef if unknown } } #-------------------------------------------------------------------------- sub e2charnum { my $in = $_[0]; return undef unless defined $in and length $in; # Convert to decimal: if($in =~ m/^(0[0-7]*)$/s ) { $in = oct $in; } elsif($in =~ m/^0?x([0-9a-fA-F]+)$/s ) { $in = hex $1; } # else it's decimal, or named if($in =~ m/^[0-9]+$/s) { return 0 + $in; } else { return $Name2character_number{$in}; # returns undef if unknown } } #-------------------------------------------------------------------------- %Code2USASCII = ( # mostly generated by # perl -e "printf qq{ \x25 3s, '\x25s',\n}, $_, chr($_) foreach (32 .. 126)" 32, ' ', 33, '!', 34, '"', 35, '#', 36, '$', 37, '%', 38, '&', 39, "'", #! 40, '(', 41, ')', 42, '*', 43, '+', 44, ',', 45, '-', 46, '.', 47, '/', 48, '0', 49, '1', 50, '2', 51, '3', 52, '4', 53, '5', 54, '6', 55, '7', 56, '8', 57, '9', 58, ':', 59, ';', 60, '<', 61, '=', 62, '>', 63, '?', 64, '@', 65, 'A', 66, 'B', 67, 'C', 68, 'D', 69, 'E', 70, 'F', 71, 'G', 72, 'H', 73, 'I', 74, 'J', 75, 'K', 76, 'L', 77, 'M', 78, 'N', 79, 'O', 80, 'P', 81, 'Q', 82, 'R', 83, 'S', 84, 'T', 85, 'U', 86, 'V', 87, 'W', 88, 'X', 89, 'Y', 90, 'Z', 91, '[', 92, "\\", #! 93, ']', 94, '^', 95, '_', 96, '`', 97, 'a', 98, 'b', 99, 'c', 100, 'd', 101, 'e', 102, 'f', 103, 'g', 104, 'h', 105, 'i', 106, 'j', 107, 'k', 108, 'l', 109, 'm', 110, 'n', 111, 'o', 112, 'p', 113, 'q', 114, 'r', 115, 's', 116, 't', 117, 'u', 118, 'v', 119, 'w', 120, 'x', 121, 'y', 122, 'z', 123, '{', 124, '|', 125, '}', 126, '~', ); #-------------------------------------------------------------------------- %Latin1Code_to_fallback = (); @Latin1Code_to_fallback{0xA0 .. 0xFF} = ( # Copied from Text/Unidecode/x00.pm: ' ', qq{!}, qq{C/}, 'PS', qq{\$?}, qq{Y=}, qq{|}, 'SS', qq{"}, qq{(c)}, 'a', qq{<<}, qq{!}, "", qq{(r)}, qq{-}, 'deg', qq{+-}, '2', '3', qq{'}, 'u', 'P', qq{*}, qq{,}, '1', 'o', qq{>>}, qq{1/4}, qq{1/2}, qq{3/4}, qq{?}, 'A', 'A', 'A', 'A', 'A', 'A', 'AE', 'C', 'E', 'E', 'E', 'E', 'I', 'I', 'I', 'I', 'D', 'N', 'O', 'O', 'O', 'O', 'O', 'x', 'O', 'U', 'U', 'U', 'U', 'U', 'Th', 'ss', 'a', 'a', 'a', 'a', 'a', 'a', 'ae', 'c', 'e', 'e', 'e', 'e', 'i', 'i', 'i', 'i', 'd', 'n', 'o', 'o', 'o', 'o', 'o', qq{/}, 'o', 'u', 'u', 'u', 'u', 'y', 'th', 'y', ); { # Now stuff %Latin1Char_to_fallback: %Latin1Char_to_fallback = (); my($k,$v); while( ($k,$v) = each %Latin1Code_to_fallback) { $Latin1Char_to_fallback{chr $k} = $v; #print chr($k), ' => ', $v, "\n"; } } #-------------------------------------------------------------------------- %Name2character_number = ( # General XML/XHTML: 'lt' => 60, 'gt' => 62, 'quot' => 34, 'amp' => 38, 'apos' => 39, # POD-specific: 'sol' => 47, 'verbar' => 124, 'lchevron' => 171, # legacy for laquo 'rchevron' => 187, # legacy for raquo # Remember, grave looks like \ (as in virtu\) # acute looks like / (as in re/sume/) # circumflex looks like ^ (as in papier ma^che/) # umlaut/dieresis looks like " (as in nai"ve, Chloe") # From the XHTML 1 .ent files: 'nbsp' , 160, 'iexcl' , 161, 'cent' , 162, 'pound' , 163, 'curren' , 164, 'yen' , 165, 'brvbar' , 166, 'sect' , 167, 'uml' , 168, 'copy' , 169, 'ordf' , 170, 'laquo' , 171, 'not' , 172, 'shy' , 173, 'reg' , 174, 'macr' , 175, 'deg' , 176, 'plusmn' , 177, 'sup2' , 178, 'sup3' , 179, 'acute' , 180, 'micro' , 181, 'para' , 182, 'middot' , 183, 'cedil' , 184, 'sup1' , 185, 'ordm' , 186, 'raquo' , 187, 'frac14' , 188, 'frac12' , 189, 'frac34' , 190, 'iquest' , 191, 'Agrave' , 192, 'Aacute' , 193, 'Acirc' , 194, 'Atilde' , 195, 'Auml' , 196, 'Aring' , 197, 'AElig' , 198, 'Ccedil' , 199, 'Egrave' , 200, 'Eacute' , 201, 'Ecirc' , 202, 'Euml' , 203, 'Igrave' , 204, 'Iacute' , 205, 'Icirc' , 206, 'Iuml' , 207, 'ETH' , 208, 'Ntilde' , 209, 'Ograve' , 210, 'Oacute' , 211, 'Ocirc' , 212, 'Otilde' , 213, 'Ouml' , 214, 'times' , 215, 'Oslash' , 216, 'Ugrave' , 217, 'Uacute' , 218, 'Ucirc' , 219, 'Uuml' , 220, 'Yacute' , 221, 'THORN' , 222, 'szlig' , 223, 'agrave' , 224, 'aacute' , 225, 'acirc' , 226, 'atilde' , 227, 'auml' , 228, 'aring' , 229, 'aelig' , 230, 'ccedil' , 231, 'egrave' , 232, 'eacute' , 233, 'ecirc' , 234, 'euml' , 235, 'igrave' , 236, 'iacute' , 237, 'icirc' , 238, 'iuml' , 239, 'eth' , 240, 'ntilde' , 241, 'ograve' , 242, 'oacute' , 243, 'ocirc' , 244, 'otilde' , 245, 'ouml' , 246, 'divide' , 247, 'oslash' , 248, 'ugrave' , 249, 'uacute' , 250, 'ucirc' , 251, 'uuml' , 252, 'yacute' , 253, 'thorn' , 254, 'yuml' , 255, 'fnof' , 402, 'Alpha' , 913, 'Beta' , 914, 'Gamma' , 915, 'Delta' , 916, 'Epsilon' , 917, 'Zeta' , 918, 'Eta' , 919, 'Theta' , 920, 'Iota' , 921, 'Kappa' , 922, 'Lambda' , 923, 'Mu' , 924, 'Nu' , 925, 'Xi' , 926, 'Omicron' , 927, 'Pi' , 928, 'Rho' , 929, 'Sigma' , 931, 'Tau' , 932, 'Upsilon' , 933, 'Phi' , 934, 'Chi' , 935, 'Psi' , 936, 'Omega' , 937, 'alpha' , 945, 'beta' , 946, 'gamma' , 947, 'delta' , 948, 'epsilon' , 949, 'zeta' , 950, 'eta' , 951, 'theta' , 952, 'iota' , 953, 'kappa' , 954, 'lambda' , 955, 'mu' , 956, 'nu' , 957, 'xi' , 958, 'omicron' , 959, 'pi' , 960, 'rho' , 961, 'sigmaf' , 962, 'sigma' , 963, 'tau' , 964, 'upsilon' , 965, 'phi' , 966, 'chi' , 967, 'psi' , 968, 'omega' , 969, 'thetasym' , 977, 'upsih' , 978, 'piv' , 982, 'bull' , 8226, 'hellip' , 8230, 'prime' , 8242, 'Prime' , 8243, 'oline' , 8254, 'frasl' , 8260, 'weierp' , 8472, 'image' , 8465, 'real' , 8476, 'trade' , 8482, 'alefsym' , 8501, 'larr' , 8592, 'uarr' , 8593, 'rarr' , 8594, 'darr' , 8595, 'harr' , 8596, 'crarr' , 8629, 'lArr' , 8656, 'uArr' , 8657, 'rArr' , 8658, 'dArr' , 8659, 'hArr' , 8660, 'forall' , 8704, 'part' , 8706, 'exist' , 8707, 'empty' , 8709, 'nabla' , 8711, 'isin' , 8712, 'notin' , 8713, 'ni' , 8715, 'prod' , 8719, 'sum' , 8721, 'minus' , 8722, 'lowast' , 8727, 'radic' , 8730, 'prop' , 8733, 'infin' , 8734, 'ang' , 8736, 'and' , 8743, 'or' , 8744, 'cap' , 8745, 'cup' , 8746, 'int' , 8747, 'there4' , 8756, 'sim' , 8764, 'cong' , 8773, 'asymp' , 8776, 'ne' , 8800, 'equiv' , 8801, 'le' , 8804, 'ge' , 8805, 'sub' , 8834, 'sup' , 8835, 'nsub' , 8836, 'sube' , 8838, 'supe' , 8839, 'oplus' , 8853, 'otimes' , 8855, 'perp' , 8869, 'sdot' , 8901, 'lceil' , 8968, 'rceil' , 8969, 'lfloor' , 8970, 'rfloor' , 8971, 'lang' , 9001, 'rang' , 9002, 'loz' , 9674, 'spades' , 9824, 'clubs' , 9827, 'hearts' , 9829, 'diams' , 9830, 'OElig' , 338, 'oelig' , 339, 'Scaron' , 352, 'scaron' , 353, 'Yuml' , 376, 'circ' , 710, 'tilde' , 732, 'ensp' , 8194, 'emsp' , 8195, 'thinsp' , 8201, 'zwnj' , 8204, 'zwj' , 8205, 'lrm' , 8206, 'rlm' , 8207, 'ndash' , 8211, 'mdash' , 8212, 'lsquo' , 8216, 'rsquo' , 8217, 'sbquo' , 8218, 'ldquo' , 8220, 'rdquo' , 8221, 'bdquo' , 8222, 'dagger' , 8224, 'Dagger' , 8225, 'permil' , 8240, 'lsaquo' , 8249, 'rsaquo' , 8250, 'euro' , 8364, ); # Fill out %Name2character... { %Name2character = (); my($name, $number); while( ($name, $number) = each %Name2character_number) { if($] < 5.007 and $number > 255) { $Name2character{$name} = $FAR_CHAR; # substitute for Unicode characters, for perls # that can't reliably handle them } elsif ($] >= 5.007003) { $Name2character{$name} = chr utf8::unicode_to_native($number); # normal case for more recent Perls where we can translate from Unicode # to the native character set. } elsif (exists $Code2USASCII{$number}) { $Name2character{$name} = $Code2USASCII{$number}; # on older Perls, we can use the translations we have hard-coded in this # file, but these don't include the non-ASCII-range characters } elsif ($NOT_ASCII && $number > 127 && $number < 256) { # this range on old non-ASCII-platform perls is wrong if (exists $Latin1Code_to_fallback{$number}) { $Name2character{$name} = $Latin1Code_to_fallback{$number}; } else { $Name2character{$name} = $FAR_CHAR; } } else { $Name2character{$name} = chr $number; } } } #-------------------------------------------------------------------------- 1; __END__ #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # What I used for reading the XHTML .ent files: my(@norms, @good, @bad); my $dir = 'c:/sgml/docbook/'; my %escapes; foreach my $file (qw( xhtml-symbol.ent xhtml-lat1.ent xhtml-special.ent )) { open(IN, "<$dir$file") or die "can't read-open $dir$file: $!"; print "Reading $file...\n"; while() { if(m//) { my($name, $value) = ($1,$2); next if $name eq 'quot' or $name eq 'apos' or $name eq 'gt'; $value = hex $1 if $value =~ m/^x([a-fA-F0-9]+)$/s; print "ILLEGAL VALUE $value" unless $value =~ m/^\d+$/s; if($value > 255) { push @good , sprintf " %-10s , chr(%s),\n", "'$name'", $value; push @bad , sprintf " %-10s , \$bad,\n", "'$name'", $value; } else { push @norms, sprintf " %-10s , chr(%s),\n", "'$name'", $value; } } elsif(m/ 0) { ## Too many arguments - assume that this is a hash and ## the user forgot to pass a reference to it. %opts = ($_, @_); } elsif (!defined $_) { $_ = ''; } elsif (ref $_) { ## User passed a ref to a hash %opts = %{$_} if (ref($_) eq 'HASH'); } elsif (/^[-+]?\d+$/) { ## User passed in the exit value to use $opts{'-exitval'} = $_; } else { ## User passed in a message to print before issuing usage. $_ and $opts{'-message'} = $_; } ## Need this for backward compatibility since we formerly used ## options that were all uppercase words rather than ones that ## looked like Unix command-line options. ## to be uppercase keywords) %opts = map { my ($key, $val) = ($_, $opts{$_}); $key =~ s/^(?=\w)/-/; $key =~ /^-msg/i and $key = '-message'; $key =~ /^-exit/i and $key = '-exitval'; lc($key) => $val; } (keys %opts); ## Now determine default -exitval and -verbose values to use if ((! defined $opts{'-exitval'}) && (! defined $opts{'-verbose'})) { $opts{'-exitval'} = 2; $opts{'-verbose'} = 0; } elsif (! defined $opts{'-exitval'}) { $opts{'-exitval'} = ($opts{'-verbose'} > 0) ? 1 : 2; } elsif (! defined $opts{'-verbose'}) { $opts{'-verbose'} = (lc($opts{'-exitval'}) eq 'noexit' || $opts{'-exitval'} < 2); } ## Default the output file $opts{'-output'} = (lc($opts{'-exitval'}) eq 'noexit' || $opts{'-exitval'} < 2) ? \*STDOUT : \*STDERR unless (defined $opts{'-output'}); ## Default the input file $opts{'-input'} = $0 unless (defined $opts{'-input'}); ## Look up input file in path if it doesn't exist. unless ((ref $opts{'-input'}) || (-e $opts{'-input'})) { my $basename = $opts{'-input'}; my $pathsep = ($^O =~ /^(?:dos|os2|MSWin32)$/i) ? ';' : (($^O eq 'MacOS' || $^O eq 'VMS') ? ',' : ':'); my $pathspec = $opts{'-pathlist'} || $ENV{PATH} || $ENV{PERL5LIB}; my @paths = (ref $pathspec) ? @$pathspec : split($pathsep, $pathspec); for my $dirname (@paths) { $_ = File::Spec->catfile($dirname, $basename) if length; last if (-e $_) && ($opts{'-input'} = $_); } } ## Now create a pod reader and constrain it to the desired sections. my $parser = new Pod::Usage(USAGE_OPTIONS => \%opts); if ($opts{'-verbose'} == 0) { $parser->select('(?:SYNOPSIS|USAGE)\s*'); } elsif ($opts{'-verbose'} == 1) { my $opt_re = '(?i)' . '(?:OPTIONS|ARGUMENTS)' . '(?:\s*(?:AND|\/)\s*(?:OPTIONS|ARGUMENTS))?'; $parser->select( '(?:SYNOPSIS|USAGE)\s*', $opt_re, "DESCRIPTION/$opt_re" ); } elsif ($opts{'-verbose'} >= 2 && $opts{'-verbose'} != 99) { $parser->select('.*'); } elsif ($opts{'-verbose'} == 99) { my $sections = $opts{'-sections'}; $parser->select( (ref $sections) ? @$sections : $sections ); $opts{'-verbose'} = 1; } ## Check for perldoc my $progpath = $opts{'-perldoc'} ? $opts{'-perldoc'} : File::Spec->catfile($Config{scriptdirexp} || $Config{scriptdir}, 'perldoc'); my $version = sprintf("%vd",$^V); if ($Config{versiononly} and $Config{startperl} =~ /\Q$version\E$/ ) { $progpath .= $version; } $opts{'-noperldoc'} = 1 unless -e $progpath; ## Now translate the pod document and then exit with the desired status if ( !$opts{'-noperldoc'} and $opts{'-verbose'} >= 2 and !ref($opts{'-input'}) and $opts{'-output'} == \*STDOUT ) { ## spit out the entire PODs. Might as well invoke perldoc print { $opts{'-output'} } ($opts{'-message'}, "\n") if($opts{'-message'}); if(defined $opts{-input} && $opts{-input} =~ /^\s*(\S.*?)\s*$/) { # the perldocs back to 5.005 should all have -F # without -F there are warnings in -T scripts my $f = $1; my @perldoc_cmd = ($progpath); if ($opts{'-perldocopt'}) { $opts{'-perldocopt'} =~ s/^\s+|\s+$//g; push @perldoc_cmd, split(/\s+/, $opts{'-perldocopt'}); } push @perldoc_cmd, ('-F', $f); unshift @perldoc_cmd, $opts{'-perlcmd'} if $opts{'-perlcmd'}; system(@perldoc_cmd); if($?) { # RT16091: fall back to more if perldoc failed system(($Config{pager} || $ENV{PAGER} || '/bin/more'), $1); } } else { croak "Unspecified input file or insecure argument.\n"; } } else { $parser->parse_from_file($opts{'-input'}, $opts{'-output'}); } exit($opts{'-exitval'}) unless (lc($opts{'-exitval'}) eq 'noexit'); } ##--------------------------------------------------------------------------- ##------------------------------- ## Method definitions begin here ##------------------------------- sub new { my $this = shift; my $class = ref($this) || $this; my %params = @_; my $self = {%params}; bless $self, $class; if ($self->can('initialize')) { $self->initialize(); } else { # pass through options to Pod::Text my %opts; for (qw(alt code indent loose margin quotes sentence stderr utf8 width)) { my $val = $params{USAGE_OPTIONS}{"-$_"}; $opts{$_} = $val if defined $val; } $self = $self->SUPER::new(%opts); %$self = (%$self, %params); } return $self; } # This subroutine was copied in whole-cloth from Pod::Select 1.60 in order to # allow the ejection of Pod::Select from the core without breaking Pod::Usage. # -- rjbs, 2013-03-18 sub _compile_section_spec { my ($section_spec) = @_; my (@regexs, $negated); ## Compile the spec into a list of regexs local $_ = $section_spec; s{\\\\}{\001}g; ## handle escaped backward slashes s{\\/}{\002}g; ## handle escaped forward slashes ## Parse the regexs for the heading titles @regexs = split(/\//, $_, $MAX_HEADING_LEVEL); ## Set default regex for ommitted levels for (my $i = 0; $i < $MAX_HEADING_LEVEL; ++$i) { $regexs[$i] = '.*' unless ((defined $regexs[$i]) && (length $regexs[$i])); } ## Modify the regexs as needed and validate their syntax my $bad_regexs = 0; for (@regexs) { $_ .= '.+' if ($_ eq '!'); s{\001}{\\\\}g; ## restore escaped backward slashes s{\002}{\\/}g; ## restore escaped forward slashes $negated = s/^\!//; ## check for negation eval "m{$_}"; ## check regex syntax if ($@) { ++$bad_regexs; carp qq{Bad regular expression /$_/ in "$section_spec": $@\n}; } else { ## Add the forward and rear anchors (and put the negator back) $_ = '^' . $_ unless (/^\^/); $_ = $_ . '$' unless (/\$$/); $_ = '!' . $_ if ($negated); } } return (! $bad_regexs) ? [ @regexs ] : undef; } sub select { my ($self, @sections) = @_; if ($ISA[0]->can('select')) { $self->SUPER::select(@sections); } else { # we're using Pod::Simple - need to mimic the behavior of Pod::Select my $add = ($sections[0] eq '+') ? shift(@sections) : ''; ## Reset the set of sections to use unless (@sections) { delete $self->{USAGE_SELECT} unless ($add); return; } $self->{USAGE_SELECT} = [] unless ($add && $self->{USAGE_SELECT}); my $sref = $self->{USAGE_SELECT}; ## Compile each spec for my $spec (@sections) { my $cs = _compile_section_spec($spec); if ( defined $cs ) { ## Store them in our sections array push(@$sref, $cs); } else { carp qq{Ignoring section spec "$spec"!\n}; } } } } # Override Pod::Text->seq_i to return just "arg", not "*arg*". sub seq_i { return $_[1] } # Override Pod::Text->cmd_i to return just "arg", not "*arg*". # newer version based on Pod::Simple sub cmd_i { return $_[2] } # This overrides the Pod::Text method to do something very akin to what # Pod::Select did as well as the work done below by preprocess_paragraph. # Note that the below is very, very specific to Pod::Text and Pod::Simple. sub _handle_element_end { my ($self, $element) = @_; if ($element eq 'head1') { $self->{USAGE_HEADINGS} = [ $$self{PENDING}[-1][1] ]; if ($self->{USAGE_OPTIONS}->{-verbose} < 2) { $$self{PENDING}[-1][1] =~ s/^\s*SYNOPSIS\s*$/USAGE/; } } elsif ($element =~ /^head(\d+)$/ && $1) { # avoid 0 my $idx = $1 - 1; $self->{USAGE_HEADINGS} = [] unless($self->{USAGE_HEADINGS}); $self->{USAGE_HEADINGS}->[$idx] = $$self{PENDING}[-1][1]; # we have to get rid of the lower headings splice(@{$self->{USAGE_HEADINGS}},$idx+1); } if ($element =~ /^head\d+$/) { $$self{USAGE_SKIPPING} = 1; if (!$$self{USAGE_SELECT} || !@{ $$self{USAGE_SELECT} }) { $$self{USAGE_SKIPPING} = 0; } else { my @headings = @{$$self{USAGE_HEADINGS}}; for my $section_spec ( @{$$self{USAGE_SELECT}} ) { my $match = 1; for (my $i = 0; $i < $MAX_HEADING_LEVEL; ++$i) { $headings[$i] = '' unless defined $headings[$i]; my $regex = $section_spec->[$i]; my $negated = ($regex =~ s/^\!//); $match &= ($negated ? ($headings[$i] !~ /${regex}/) : ($headings[$i] =~ /${regex}/)); last unless ($match); } # end heading levels if ($match) { $$self{USAGE_SKIPPING} = 0; last; } } # end sections } # Try to do some lowercasing instead of all-caps in headings, and use # a colon to end all headings. if($self->{USAGE_OPTIONS}->{-verbose} < 2) { local $_ = $$self{PENDING}[-1][1]; s{([A-Z])([A-Z]+)}{((length($2) > 2) ? $1 : lc($1)) . lc($2)}ge; s/\s*$/:/ unless (/:\s*$/); $_ .= "\n"; $$self{PENDING}[-1][1] = $_; } } if ($$self{USAGE_SKIPPING} && $element !~ m/^over-|^[BCFILSZ]$/) { pop @{ $$self{PENDING} }; } else { $self->SUPER::_handle_element_end($element); } } # required for Pod::Simple API sub start_document { my $self = shift; $self->SUPER::start_document(); my $msg = $self->{USAGE_OPTIONS}->{-message} or return 1; my $out_fh = $self->output_fh(); print $out_fh "$msg\n"; } # required for old Pod::Parser API sub begin_pod { my $self = shift; $self->SUPER::begin_pod(); ## Have to call superclass my $msg = $self->{USAGE_OPTIONS}->{-message} or return 1; my $out_fh = $self->output_handle(); print $out_fh "$msg\n"; } sub preprocess_paragraph { my $self = shift; local $_ = shift; my $line = shift; ## See if this is a heading and we aren't printing the entire manpage. if (($self->{USAGE_OPTIONS}->{-verbose} < 2) && /^=head/) { ## Change the title of the SYNOPSIS section to USAGE s/^=head1\s+SYNOPSIS\s*$/=head1 USAGE/; ## Try to do some lowercasing instead of all-caps in headings s{([A-Z])([A-Z]+)}{((length($2) > 2) ? $1 : lc($1)) . lc($2)}ge; ## Use a colon to end all headings s/\s*$/:/ unless (/:\s*$/); $_ .= "\n"; } return $self->SUPER::preprocess_paragraph($_); } 1; # keep require happy __END__ ############################################################################# # Pod/Find.pm -- finds files containing POD documentation # # Author: Marek Rouchal # # Copyright (C) 1999-2000 by Marek Rouchal (and borrowing code # from Nick Ing-Simmon's PodToHtml). All rights reserved. # This file is part of "PodParser". Pod::Find is free software; # you can redistribute it and/or modify it under the same terms # as Perl itself. ############################################################################# package Pod::Find; use strict; use vars qw($VERSION); $VERSION = '1.63'; ## Current version of this package require 5.005; ## requires this Perl version or later use Carp; BEGIN { if ($] < 5.006) { require Symbol; import Symbol; } } ############################################################################# #use diagnostics; use Exporter; use File::Spec; use File::Find; use Cwd qw(abs_path cwd); use vars qw(@ISA @EXPORT_OK $VERSION); @ISA = qw(Exporter); @EXPORT_OK = qw(&pod_find &simplify_name &pod_where &contains_pod); # package global variables my $SIMPLIFY_RX; # return a hash of the POD files found # first argument may be a hashref (options), # rest is a list of directories to search recursively sub pod_find { my %opts; if(ref $_[0]) { %opts = %{shift()}; } $opts{-verbose} ||= 0; $opts{-perl} ||= 0; my (@search) = @_; if($opts{-script}) { require Config; push(@search, $Config::Config{scriptdir}) if -d $Config::Config{scriptdir}; $opts{-perl} = 1; } if($opts{-inc}) { if ($^O eq 'MacOS') { # tolerate '.', './some_dir' and '(../)+some_dir' on Mac OS my @new_INC = @INC; for (@new_INC) { if ( $_ eq '.' ) { $_ = ':'; } elsif ( $_ =~ s{^((?:\.\./)+)}{':' x (length($1)/3)}e ) { $_ = ':'. $_; } else { $_ =~ s{^\./}{:}; } } push(@search, grep($_ ne File::Spec->curdir, @new_INC)); } else { my %seen; my $curdir = File::Spec->curdir; foreach(@INC) { next if $_ eq $curdir; my $path = abs_path($_); push(@search, $path) unless $seen{$path}++; } } $opts{-perl} = 1; } if($opts{-perl}) { require Config; # this code simplifies the POD name for Perl modules: # * remove "site_perl" # * remove e.g. "i586-linux" (from 'archname') # * remove e.g. 5.00503 # * remove pod/ if followed by *.pod (e.g. in pod/perlfunc.pod) # Mac OS: # * remove ":?site_perl:" # * remove :?pod: if followed by *.pod (e.g. in :pod:perlfunc.pod) if ($^O eq 'MacOS') { $SIMPLIFY_RX = qq!^(?i:\:?site_perl\:|\:?pod\:(?=.*?\\.pod\\z))*!; } else { $SIMPLIFY_RX = qq!^(?i:site(_perl)?/|\Q$Config::Config{archname}\E/|\\d+\\.\\d+([_.]?\\d+)?/|pod/(?=.*?\\.pod\\z))*!; } } my %dirs_visited; my %pods; my %names; my $pwd = cwd(); foreach my $try (@search) { unless(File::Spec->file_name_is_absolute($try)) { # make path absolute $try = File::Spec->catfile($pwd,$try); } # simplify path # on VMS canonpath will vmsify:[the.path], but File::Find::find # wants /unixy/paths if ($^O eq 'VMS') { $try = VMS::Filespec::unixify($try); } else { $try = File::Spec->canonpath($try); } my $name; if(-f $try) { if($name = _check_and_extract_name($try, $opts{-verbose})) { _check_for_duplicates($try, $name, \%names, \%pods); } next; } my $root_rx = $^O eq 'MacOS' ? qq!^\Q$try\E! : qq!^\Q$try\E/!; $root_rx=~ s|//$|/|; # remove trailing double slash File::Find::find( sub { my $item = $File::Find::name; if(-d) { if($item =~ m{/(?:RCS|CVS|SCCS|\.svn|\.hg|\.git|\.sync)$}) { $File::Find::prune = 1; return; } elsif($dirs_visited{$item}) { warn "Directory '$item' already seen, skipping.\n" if($opts{-verbose}); $File::Find::prune = 1; return; } else { $dirs_visited{$item} = 1; } if($opts{-perl} && /^(\d+\.[\d_]+)\z/s && eval "$1" != $]) { $File::Find::prune = 1; warn "Perl $] version mismatch on $_, skipping.\n" if($opts{-verbose}); } return; } if($name = _check_and_extract_name($item, $opts{-verbose}, $root_rx)) { _check_for_duplicates($item, $name, \%names, \%pods); } }, $try); # end of File::Find::find } chdir $pwd; return %pods; } sub _check_for_duplicates { my ($file, $name, $names_ref, $pods_ref) = @_; if($$names_ref{$name}) { warn "Duplicate POD found (shadowing?): $name ($file)\n"; warn ' Already seen in ', join(' ', grep($$pods_ref{$_} eq $name, keys %$pods_ref)),"\n"; } else { $$names_ref{$name} = 1; } return $$pods_ref{$file} = $name; } sub _check_and_extract_name { my ($file, $verbose, $root_rx) = @_; # check extension or executable flag # this involves testing the .bat extension on Win32! unless(-f $file && -T $file && ($file =~ /\.(pod|pm|plx?)\z/i || -x $file )) { return; } return unless contains_pod($file,$verbose); # strip non-significant path components # TODO what happens on e.g. Win32? my $name = $file; if(defined $root_rx) { $name =~ s/$root_rx//is; $name =~ s/$SIMPLIFY_RX//is if(defined $SIMPLIFY_RX); } else { if ($^O eq 'MacOS') { $name =~ s/^.*://s; } else { $name =~ s{^.*/}{}s; } } _simplify($name); $name =~ s{/+}{::}g; if ($^O eq 'MacOS') { $name =~ s{:+}{::}g; # : -> :: } else { $name =~ s{/+}{::}g; # / -> :: } return $name; } # basic simplification of the POD name: # basename & strip extension sub simplify_name { my ($str) = @_; # remove all path components if ($^O eq 'MacOS') { $str =~ s/^.*://s; } else { $str =~ s{^.*/}{}s; } _simplify($str); return $str; } # internal sub only sub _simplify { # strip Perl's own extensions $_[0] =~ s/\.(pod|pm|plx?)\z//i; # strip meaningless extensions on Win32 and OS/2 $_[0] =~ s/\.(bat|exe|cmd)\z//i if($^O =~ /mswin|os2/i); # strip meaningless extensions on VMS $_[0] =~ s/\.(com)\z//i if($^O eq 'VMS'); } # contribution from Tim Jenness sub pod_where { # default options my %options = ( '-inc' => 0, '-verbose' => 0, '-dirs' => [ File::Spec->curdir ], ); # Check for an options hash as first argument if (defined $_[0] && ref($_[0]) eq 'HASH') { my $opt = shift; # Merge default options with supplied options %options = (%options, %$opt); } # Check usage carp 'Usage: pod_where({options}, $pod)' unless (scalar(@_)); # Read argument my $pod = shift; # Split on :: and then join the name together using File::Spec my @parts = split (/::/, $pod); # Get full directory list my @search_dirs = @{ $options{'-dirs'} }; if ($options{'-inc'}) { require Config; # Add @INC if ($^O eq 'MacOS' && $options{'-inc'}) { # tolerate '.', './some_dir' and '(../)+some_dir' on Mac OS my @new_INC = @INC; for (@new_INC) { if ( $_ eq '.' ) { $_ = ':'; } elsif ( $_ =~ s{^((?:\.\./)+)}{':' x (length($1)/3)}e ) { $_ = ':'. $_; } else { $_ =~ s{^\./}{:}; } } push (@search_dirs, @new_INC); } elsif ($options{'-inc'}) { push (@search_dirs, @INC); } # Add location of pod documentation for perl man pages (eg perlfunc) # This is a pod directory in the private install tree #my $perlpoddir = File::Spec->catdir($Config::Config{'installprivlib'}, # 'pod'); #push (@search_dirs, $perlpoddir) # if -d $perlpoddir; # Add location of binaries such as pod2text push (@search_dirs, $Config::Config{'scriptdir'}) if -d $Config::Config{'scriptdir'}; } warn 'Search path is: '.join(' ', @search_dirs)."\n" if $options{'-verbose'}; # Loop over directories Dir: foreach my $dir ( @search_dirs ) { # Don't bother if can't find the directory if (-d $dir) { warn "Looking in directory $dir\n" if $options{'-verbose'}; # Now concatenate this directory with the pod we are searching for my $fullname = File::Spec->catfile($dir, @parts); $fullname = VMS::Filespec::unixify($fullname) if $^O eq 'VMS'; warn "Filename is now $fullname\n" if $options{'-verbose'}; # Loop over possible extensions foreach my $ext ('', '.pod', '.pm', '.pl') { my $fullext = $fullname . $ext; if (-f $fullext && contains_pod($fullext, $options{'-verbose'}) ) { warn "FOUND: $fullext\n" if $options{'-verbose'}; return $fullext; } } } else { warn "Directory $dir does not exist\n" if $options{'-verbose'}; next Dir; } # for some strange reason the path on MacOS/darwin/cygwin is # 'pods' not 'pod' # this could be the case also for other systems that # have a case-tolerant file system, but File::Spec # does not recognize 'darwin' yet. And cygwin also has "pods", # but is not case tolerant. Oh well... if((File::Spec->case_tolerant || $^O =~ /macos|darwin|cygwin/i) && -d File::Spec->catdir($dir,'pods')) { $dir = File::Spec->catdir($dir,'pods'); redo Dir; } if(-d File::Spec->catdir($dir,'pod')) { $dir = File::Spec->catdir($dir,'pod'); redo Dir; } } # No match; return; } sub contains_pod { my $file = shift; my $verbose = 0; $verbose = shift if @_; # check for one line of POD my $podfh; if ($] < 5.006) { $podfh = gensym(); } unless(open($podfh,"<$file")) { warn "Error: $file is unreadable: $!\n"; return; } local $/ = undef; my $pod = <$podfh>; close($podfh) || die "Error closing $file: $!\n"; unless($pod =~ /^=(head\d|pod|over|item|cut)\b/m) { warn "No POD in $file, skipping.\n" if($verbose); return 0; } return 1; } 1; package Pod::Functions; use strict; our $VERSION = '1.13'; require Exporter; our @ISA = qw(Exporter); our @EXPORT = qw(%Kinds %Type %Flavor %Type_Description @Type_Order); our(%Kinds, %Type, %Flavor, %Type_Description, @Type_Order); foreach ( [String => 'Functions for SCALARs or strings'], [Regexp => 'Regular expressions and pattern matching'], [Math => 'Numeric functions'], [ARRAY => 'Functions for real @ARRAYs'], [LIST => 'Functions for list data'], [HASH => 'Functions for real %HASHes'], ['I/O' => 'Input and output functions'], [Binary => 'Functions for fixed-length data or records'], [File => 'Functions for filehandles, files, or directories'], [Flow => 'Keywords related to the control flow of your Perl program'], [Namespace => 'Keywords related to scoping'], [Misc => 'Miscellaneous functions'], [Process => 'Functions for processes and process groups'], [Modules => 'Keywords related to Perl modules'], [Objects => 'Keywords related to classes and object-orientation'], [Socket => 'Low-level socket functions'], [SysV => 'System V interprocess communication functions'], [User => 'Fetching user and group info'], [Network => 'Fetching network info'], [Time => 'Time-related functions'], ) { push @Type_Order, $_->[0]; $Type_Description{$_->[0]} = $_->[1]; }; while () { chomp; s/^#.*//; next unless $_; my($name, @data) = split "\t", $_; $Flavor{$name} = pop @data; $Type{$name} = join ',', @data; for my $t (@data) { push @{$Kinds{$t}}, $name; } } close DATA; my( $typedesc, $list ); unless (caller) { foreach my $type ( @Type_Order ) { $list = join(", ", sort @{$Kinds{$type}}); $typedesc = $Type_Description{$type} . ":"; write; } } format = ^<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< $typedesc ~~ ^<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< $typedesc ~~ ^<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< $list . 1; __DATA__ -X File a file test (-r, -x, etc) abs Math absolute value function accept Socket accept an incoming socket connect alarm Process schedule a SIGALRM atan2 Math arctangent of Y/X in the range -PI to PI bind Socket binds an address to a socket binmode I/O prepare binary files for I/O bless Objects create an object break Flow break out of a C block caller Flow Namespace get context of the current subroutine call chdir File change your current working directory chmod File changes the permissions on a list of files chomp String remove a trailing record separator from a string chop String remove the last character from a string chown File change the ownership on a list of files chr String get character this number represents chroot File make directory new root for path lookups close I/O close file (or pipe or socket) handle closedir I/O close directory handle connect Socket connect to a remote socket continue Flow optional trailing block in a while or foreach cos Math cosine function crypt String one-way passwd-style encryption dbmclose I/O Objects breaks binding on a tied dbm file dbmopen I/O Objects create binding on a tied dbm file defined Misc test whether a value, variable, or function is defined delete HASH deletes a value from a hash die Flow I/O raise an exception or bail out do Flow Modules turn a BLOCK into a TERM dump Flow create an immediate core dump each ARRAY HASH retrieve the next key/value pair from a hash endgrent User be done using group file endhostent User be done using hosts file endnetent User be done using networks file endprotoent Network be done using protocols file endpwent User be done using passwd file endservent Network be done using services file eof I/O test a filehandle for its end eval Flow catch exceptions or compile and run code evalbytes Flow similar to string eval, but intend to parse a bytestream exec Process abandon this program to run another exists HASH test whether a hash key is present exit Flow terminate this program exp Math raise I to a power fc String return casefolded version of a string fcntl File file control system call __FILE__ Flow the name of the current source file fileno I/O return file descriptor from filehandle flock I/O lock an entire file with an advisory lock fork Process create a new process just like this one format I/O declare a picture format with use by the write() function formline Misc internal function used for formats getc I/O get the next character from the filehandle getgrent User get next group record getgrgid User get group record given group user ID getgrnam User get group record given group name gethostbyaddr Network get host record given its address gethostbyname Network get host record given name gethostent Network get next hosts record getlogin User return who logged in at this tty getnetbyaddr Network get network record given its address getnetbyname Network get networks record given name getnetent Network get next networks record getpeername Socket find the other end of a socket connection getpgrp Process get process group getppid Process get parent process ID getpriority Process get current nice value getprotobyname Network get protocol record given name getprotobynumber Network get protocol record numeric protocol getprotoent Network get next protocols record getpwent User get next passwd record getpwnam User get passwd record given user login name getpwuid User get passwd record given user ID getservbyname Network get services record given its name getservbyport Network get services record given numeric port getservent Network get next services record getsockname Socket retrieve the sockaddr for a given socket getsockopt Socket get socket options on a given socket glob File expand filenames using wildcards gmtime Time convert UNIX time into record or string using Greenwich time goto Flow create spaghetti code grep LIST locate elements in a list test true against a given criterion hex Math String convert a hexadecimal string to a number import Modules Namespace patch a module's namespace into your own index String find a substring within a string int Math get the integer portion of a number ioctl File system-dependent device control system call join LIST join a list into a string using a separator keys ARRAY HASH retrieve list of indices from a hash kill Process send a signal to a process or process group last Flow exit a block prematurely lc String return lower-case version of a string lcfirst String return a string with just the next letter in lower case length String return the number of characters in a string __LINE__ Flow the current source line number link File create a hard link in the filesystem listen Socket register your socket as a server local Namespace create a temporary value for a global variable (dynamic scoping) localtime Time convert UNIX time into record or string using local time lock Misc get a thread lock on a variable, subroutine, or method log Math retrieve the natural logarithm for a number lstat File stat a symbolic link m// Regexp match a string with a regular expression pattern map LIST apply a change to a list to get back a new list with the changes mkdir File create a directory msgctl SysV SysV IPC message control operations msgget SysV get SysV IPC message queue msgrcv SysV receive a SysV IPC message from a message queue msgsnd SysV send a SysV IPC message to a message queue my Namespace declare and assign a local variable (lexical scoping) next Flow iterate a block prematurely no Modules unimport some module symbols or semantics at compile time oct Math String convert a string to an octal number open File open a file, pipe, or descriptor opendir File open a directory ord String find a character's numeric representation our Namespace declare and assign a package variable (lexical scoping) pack Binary String convert a list into a binary representation package Modules Namespace Objects declare a separate global namespace __PACKAGE__ Flow the current package pipe Process open a pair of connected filehandles pop ARRAY remove the last element from an array and return it pos Regexp find or set the offset for the last/next m//g search print I/O output a list to a filehandle printf I/O output a formatted list to a filehandle prototype Misc get the prototype (if any) of a subroutine push ARRAY append one or more elements to an array q/STRING/ String singly quote a string qq/STRING/ String doubly quote a string qr/STRING/ Regexp compile pattern quotemeta Regexp quote regular expression magic characters qw/STRING/ LIST quote a list of words qx/STRING/ Process backquote quote a string rand Math retrieve the next pseudorandom number read Binary I/O fixed-length buffered input from a filehandle readdir I/O get a directory from a directory handle readline I/O fetch a record from a file readlink File determine where a symbolic link is pointing readpipe Process execute a system command and collect standard output recv Socket receive a message over a Socket redo Flow start this loop iteration over again ref Objects find out the type of thing being referenced rename File change a filename require Modules load in external functions from a library at runtime reset Misc clear all variables of a given name return Flow get out of a function early reverse LIST String flip a string or a list rewinddir I/O reset directory handle rindex String right-to-left substring search rmdir File remove a directory s/// Regexp replace a pattern with a string say I/O output a list to a filehandle, appending a newline scalar Misc force a scalar context seek I/O reposition file pointer for random-access I/O seekdir I/O reposition directory pointer select File I/O reset default output or do I/O multiplexing semctl SysV SysV semaphore control operations semget SysV get set of SysV semaphores semop SysV SysV semaphore operations send Socket send a message over a socket setgrent User prepare group file for use sethostent Network prepare hosts file for use setnetent Network prepare networks file for use setpgrp Process set the process group of a process setpriority Process set a process's nice value setprotoent Network prepare protocols file for use setpwent User prepare passwd file for use setservent Network prepare services file for use setsockopt Socket set some socket options shift ARRAY remove the first element of an array, and return it shmctl SysV SysV shared memory operations shmget SysV get SysV shared memory segment identifier shmread SysV read SysV shared memory shmwrite SysV write SysV shared memory shutdown Socket close down just half of a socket connection sin Math return the sine of a number sleep Process block for some number of seconds socket Socket create a socket socketpair Socket create a pair of sockets sort LIST sort a list of values splice ARRAY add or remove elements anywhere in an array split Regexp split up a string using a regexp delimiter sprintf String formatted print into a string sqrt Math square root function srand Math seed the random number generator stat File get a file's status information state Namespace declare and assign a persistent lexical variable study Regexp no-op, formerly optimized input data for repeated searches sub Flow declare a subroutine, possibly anonymously __SUB__ Flow the current subroutine, or C if not in a subroutine substr String get or alter a portion of a string symlink File create a symbolic link to a file syscall Binary I/O execute an arbitrary system call sysopen File open a file, pipe, or descriptor sysread Binary I/O fixed-length unbuffered input from a filehandle sysseek Binary I/O position I/O pointer on handle used with sysread and syswrite system Process run a separate program syswrite Binary I/O fixed-length unbuffered output to a filehandle tell I/O get current seekpointer on a filehandle telldir I/O get current seekpointer on a directory handle tie Objects bind a variable to an object class tied Objects get a reference to the object underlying a tied variable time Time return number of seconds since 1970 times Process Time return elapsed time for self and child processes tr/// String transliterate a string truncate I/O shorten a file uc String return upper-case version of a string ucfirst String return a string with just the next letter in upper case umask File set file creation mode mask undef Misc remove a variable or function definition unlink File remove one link to a file unpack Binary LIST convert binary structure into normal perl variables unshift ARRAY prepend more elements to the beginning of a list untie Objects break a tie binding to a variable use Modules Namespace Objects load in a module at compile time and import its namespace utime File set a file's last access and modify times values ARRAY HASH return a list of the values in a hash vec Binary test or set particular bits in a string wait Process wait for any child process to die waitpid Process wait for a particular child process to die wantarray Flow get void vs scalar vs list context of current subroutine call warn I/O print debugging info write I/O print a picture record y/// String transliterate a string require 5; package Pod::Simple; use strict; use Carp (); BEGIN { *DEBUG = sub () {0} unless defined &DEBUG } use integer; use Pod::Escapes 1.04 (); use Pod::Simple::LinkSection (); use Pod::Simple::BlackBox (); #use utf8; use vars qw( $VERSION @ISA @Known_formatting_codes @Known_directives %Known_formatting_codes %Known_directives $NL ); @ISA = ('Pod::Simple::BlackBox'); $VERSION = '3.35'; @Known_formatting_codes = qw(I B C L E F S X Z); %Known_formatting_codes = map(($_=>1), @Known_formatting_codes); @Known_directives = qw(head1 head2 head3 head4 item over back); %Known_directives = map(($_=>'Plain'), @Known_directives); $NL = $/ unless defined $NL; #----------------------------------------------------------------------------- # Set up some constants: BEGIN { if(defined &ASCII) { } elsif(chr(65) eq 'A') { *ASCII = sub () {1} } else { *ASCII = sub () {''} } unless(defined &MANY_LINES) { *MANY_LINES = sub () {20} } DEBUG > 4 and print STDERR "MANY_LINES is ", MANY_LINES(), "\n"; unless(MANY_LINES() >= 1) { die "MANY_LINES is too small (", MANY_LINES(), ")!\nAborting"; } if(defined &UNICODE) { } elsif($] >= 5.008) { *UNICODE = sub() {1} } else { *UNICODE = sub() {''} } } if(DEBUG > 2) { print STDERR "# We are ", ASCII ? '' : 'not ', "in ASCII-land\n"; print STDERR "# We are under a Unicode-safe Perl.\n"; } # The NO BREAK SPACE and SOFT HYHPEN are used in several submodules. if ($] ge 5.007_003) { # On sufficiently modern Perls we can handle any # character set $Pod::Simple::nbsp = chr utf8::unicode_to_native(0xA0); $Pod::Simple::shy = chr utf8::unicode_to_native(0xAD); } elsif (Pod::Simple::ASCII) { # Hard code ASCII early Perl $Pod::Simple::nbsp = "\xA0"; $Pod::Simple::shy = "\xAD"; } else { # EBCDIC on early Perl. We know what the values are for the code # pages supported then. $Pod::Simple::nbsp = "\x41"; $Pod::Simple::shy = "\xCA"; } # Design note: # This is a parser for Pod. It is not a parser for the set of Pod-like # languages which happens to contain Pod -- it is just for Pod, plus possibly # some extensions. # @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ #@ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ #@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ __PACKAGE__->_accessorize( 'nbsp_for_S', # Whether to map S<...>'s to \xA0 characters 'source_filename', # Filename of the source, for use in warnings 'source_dead', # Whether to consider this parser's source dead 'output_fh', # The filehandle we're writing to, if applicable. # Used only in some derived classes. 'hide_line_numbers', # For some dumping subclasses: whether to pointedly # suppress the start_line attribute 'line_count', # the current line number 'pod_para_count', # count of pod paragraphs seen so far 'no_whining', # whether to suppress whining 'no_errata_section', # whether to suppress the errata section 'complain_stderr', # whether to complain to stderr 'doc_has_started', # whether we've fired the open-Document event yet 'bare_output', # For some subclasses: whether to prepend # header-code and postpend footer-code 'keep_encoding_directive', # whether to emit =encoding 'nix_X_codes', # whether to ignore X<...> codes 'merge_text', # whether to avoid breaking a single piece of # text up into several events 'preserve_whitespace', # whether to try to keep whitespace as-is 'strip_verbatim_indent', # What indent to strip from verbatim 'parse_characters', # Whether parser should expect chars rather than octets 'content_seen', # whether we've seen any real Pod content 'errors_seen', # TODO: document. whether we've seen any errors (fatal or not) 'codes_in_verbatim', # for PseudoPod extensions 'code_handler', # coderef to call when a code (non-pod) line is seen 'cut_handler', # ... when a =cut line is seen 'pod_handler', # ... when a =pod line is seen 'whiteline_handler', # ... when a line with only whitespace is seen #Called like: # $code_handler->($line, $self->{'line_count'}, $self) if $code_handler; # $cut_handler->($line, $self->{'line_count'}, $self) if $cut_handler; # $pod_handler->($line, $self->{'line_count'}, $self) if $pod_handler; # $wl_handler->($line, $self->{'line_count'}, $self) if $wl_handler; 'parse_empty_lists', # whether to acknowledge empty =over/=back blocks 'raw_mode', # to report entire raw lines instead of Pod elements ); #@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ sub any_errata_seen { # good for using as an exit() value... return shift->{'errors_seen'} || 0; } sub errata_seen { return shift->{'all_errata'} || {}; } # Returns the encoding only if it was recognized as being handled and set sub detected_encoding { return shift->{'detected_encoding'}; } sub encoding { my $this = shift; return $this->{'encoding'} unless @_; # GET. $this->_handle_encoding_line("=encoding $_[0]"); if ($this->{'_processed_encoding'}) { delete $this->{'_processed_encoding'}; if(! $this->{'encoding_command_statuses'} ) { DEBUG > 2 and print STDERR " CRAZY ERROR: encoding wasn't really handled?!\n"; } elsif( $this->{'encoding_command_statuses'}[-1] ) { $this->scream( "=encoding $_[0]", sprintf "Couldn't do %s: %s", $this->{'encoding_command_reqs' }[-1], $this->{'encoding_command_statuses'}[-1], ); } else { DEBUG > 2 and print STDERR " (encoding successfully handled.)\n"; } return $this->{'encoding'}; } else { return undef; } } #@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ # Pull in some functions that, for some reason, I expect to see here too: BEGIN { *pretty = \&Pod::Simple::BlackBox::pretty; *stringify_lol = \&Pod::Simple::BlackBox::stringify_lol; } #@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ sub version_report { my $class = ref($_[0]) || $_[0]; if($class eq __PACKAGE__) { return "$class $VERSION"; } else { my $v = $class->VERSION; return "$class $v (" . __PACKAGE__ . " $VERSION)"; } } #@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #sub curr_open { # read-only list accessor # return @{ $_[0]{'curr_open'} || return() }; #} #sub _curr_open_listref { $_[0]{'curr_open'} ||= [] } sub output_string { # Works by faking out output_fh. Simplifies our code. # my $this = shift; return $this->{'output_string'} unless @_; # GET. require Pod::Simple::TiedOutFH; my $x = (defined($_[0]) and ref($_[0])) ? $_[0] : \( $_[0] ); $$x = '' unless defined $$x; DEBUG > 4 and print STDERR "# Output string set to $x ($$x)\n"; $this->{'output_fh'} = Pod::Simple::TiedOutFH->handle_on($_[0]); return $this->{'output_string'} = $_[0]; #${ ${ $this->{'output_fh'} } }; } sub abandon_output_string { $_[0]->abandon_output_fh; delete $_[0]{'output_string'} } sub abandon_output_fh { $_[0]->output_fh(undef) } # These don't delete the string or close the FH -- they just delete our # references to it/them. # TODO: document these #@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ sub new { # takes no parameters my $class = ref($_[0]) || $_[0]; #Carp::croak(__PACKAGE__ . " is a virtual base class -- see perldoc " # . __PACKAGE__ ); return bless { 'accept_codes' => { map( ($_=>$_), @Known_formatting_codes ) }, 'accept_directives' => { %Known_directives }, 'accept_targets' => {}, }, $class; } # TODO: an option for whether to interpolate E<...>'s, or just resolve to codes. #@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ sub _handle_element_start { # OVERRIDE IN DERIVED CLASS my($self, $element_name, $attr_hash_r) = @_; return; } sub _handle_element_end { # OVERRIDE IN DERIVED CLASS my($self, $element_name) = @_; return; } sub _handle_text { # OVERRIDE IN DERIVED CLASS my($self, $text) = @_; return; } #@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ # # And now directives (not targets) sub accept_directive_as_verbatim { shift->_accept_directives('Verbatim', @_) } sub accept_directive_as_data { shift->_accept_directives('Data', @_) } sub accept_directive_as_processed { shift->_accept_directives('Plain', @_) } sub _accept_directives { my($this, $type) = splice @_,0,2; foreach my $d (@_) { next unless defined $d and length $d; Carp::croak "\"$d\" isn't a valid directive name" unless $d =~ m/^[a-zA-Z][a-zA-Z0-9]*$/s; Carp::croak "\"$d\" is already a reserved Pod directive name" if exists $Known_directives{$d}; $this->{'accept_directives'}{$d} = $type; DEBUG > 2 and print STDERR "Learning to accept \"=$d\" as directive of type $type\n"; } DEBUG > 6 and print STDERR "$this\'s accept_directives : ", pretty($this->{'accept_directives'}), "\n"; return sort keys %{ $this->{'accept_directives'} } if wantarray; return; } #-------------------------------------------------------------------------- # TODO: document these: sub unaccept_directive { shift->unaccept_directives(@_) }; sub unaccept_directives { my $this = shift; foreach my $d (@_) { next unless defined $d and length $d; Carp::croak "\"$d\" isn't a valid directive name" unless $d =~ m/^[a-zA-Z][a-zA-Z0-9]*$/s; Carp::croak "But you must accept \"$d\" directives -- it's a builtin!" if exists $Known_directives{$d}; delete $this->{'accept_directives'}{$d}; DEBUG > 2 and print STDERR "OK, won't accept \"=$d\" as directive.\n"; } return sort keys %{ $this->{'accept_directives'} } if wantarray; return } #@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ # # And now targets (not directives) sub accept_target { shift->accept_targets(@_) } # alias sub accept_target_as_text { shift->accept_targets_as_text(@_) } # alias sub accept_targets { shift->_accept_targets('1', @_) } sub accept_targets_as_text { shift->_accept_targets('force_resolve', @_) } # forces them to be processed, even when there's no ":". sub _accept_targets { my($this, $type) = splice @_,0,2; foreach my $t (@_) { next unless defined $t and length $t; # TODO: enforce some limitations on what a target name can be? $this->{'accept_targets'}{$t} = $type; DEBUG > 2 and print STDERR "Learning to accept \"$t\" as target of type $type\n"; } return sort keys %{ $this->{'accept_targets'} } if wantarray; return; } #-------------------------------------------------------------------------- sub unaccept_target { shift->unaccept_targets(@_) } sub unaccept_targets { my $this = shift; foreach my $t (@_) { next unless defined $t and length $t; # TODO: enforce some limitations on what a target name can be? delete $this->{'accept_targets'}{$t}; DEBUG > 2 and print STDERR "OK, won't accept \"$t\" as target.\n"; } return sort keys %{ $this->{'accept_targets'} } if wantarray; return; } #@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ # # And now codes (not targets or directives) # XXX Probably it is an error that the digit '9' is excluded from these re's. # Broken for early Perls on EBCDIC my $xml_name_re = eval "qr/[^-.0-8:A-Z_a-z[:^ascii:]]/"; if (! defined $xml_name_re) { $xml_name_re = qr/[\x00-\x2C\x2F\x39\x3B-\x40\x5B-\x5E\x60\x7B-\x7F]/; } sub accept_code { shift->accept_codes(@_) } # alias sub accept_codes { # Add some codes my $this = shift; foreach my $new_code (@_) { next unless defined $new_code and length $new_code; # A good-enough check that it's good as an XML Name symbol: Carp::croak "\"$new_code\" isn't a valid element name" if $new_code =~ $xml_name_re # Characters under 0x80 that aren't legal in an XML Name. or $new_code =~ m/^[-\.0-9]/s or $new_code =~ m/:[-\.0-9]/s; # The legal under-0x80 Name characters that # an XML Name still can't start with. $this->{'accept_codes'}{$new_code} = $new_code; # Yes, map to itself -- just so that when we # see "=extend W [whatever] thatelementname", we say that W maps # to whatever $this->{accept_codes}{thatelementname} is, # i.e., "thatelementname". Then when we go re-mapping, # a "W" in the treelet turns into "thatelementname". We only # remap once. # If we say we accept "W", then a "W" in the treelet simply turns # into "W". } return; } #-------------------------------------------------------------------------- sub unaccept_code { shift->unaccept_codes(@_) } sub unaccept_codes { # remove some codes my $this = shift; foreach my $new_code (@_) { next unless defined $new_code and length $new_code; # A good-enough check that it's good as an XML Name symbol: Carp::croak "\"$new_code\" isn't a valid element name" if $new_code =~ $xml_name_re # Characters under 0x80 that aren't legal in an XML Name. or $new_code =~ m/^[-\.0-9]/s or $new_code =~ m/:[-\.0-9]/s; # The legal under-0x80 Name characters that # an XML Name still can't start with. Carp::croak "But you must accept \"$new_code\" codes -- it's a builtin!" if grep $new_code eq $_, @Known_formatting_codes; delete $this->{'accept_codes'}{$new_code}; DEBUG > 2 and print STDERR "OK, won't accept the code $new_code<...>.\n"; } return; } #@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ sub parse_string_document { my $self = shift; my @lines; foreach my $line_group (@_) { next unless defined $line_group and length $line_group; pos($line_group) = 0; while($line_group =~ m/([^\n\r]*)(\r?\n?)/g # supports \r, \n ,\r\n #m/([^\n\r]*)((?:\r?\n)?)/g ) { #print(">> $1\n"), $self->parse_lines($1) if length($1) or length($2) or pos($line_group) != length($line_group); # I.e., unless it's a zero-length "empty line" at the very # end of "foo\nbar\n" (i.e., between the \n and the EOS). } } $self->parse_lines(undef); # to signal EOF return $self; } sub _init_fh_source { my($self, $source) = @_; #DEBUG > 1 and print STDERR "Declaring $source as :raw for starters\n"; #$self->_apply_binmode($source, ':raw'); #binmode($source, ":raw"); return; } #:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:. # sub parse_file { my($self, $source) = (@_); if(!defined $source) { Carp::croak("Can't use empty-string as a source for parse_file"); } elsif(ref(\$source) eq 'GLOB') { $self->{'source_filename'} = '' . ($source); } elsif(ref $source) { $self->{'source_filename'} = '' . ($source); } elsif(!length $source) { Carp::croak("Can't use empty-string as a source for parse_file"); } else { { local *PODSOURCE; open(PODSOURCE, "<$source") || Carp::croak("Can't open $source: $!"); $self->{'source_filename'} = $source; $source = *PODSOURCE{IO}; } $self->_init_fh_source($source); } # By here, $source is a FH. $self->{'source_fh'} = $source; my($i, @lines); until( $self->{'source_dead'} ) { splice @lines; for($i = MANY_LINES; $i--;) { # read those many lines at a time local $/ = $NL; push @lines, scalar(<$source>); # readline last unless defined $lines[-1]; # but pass thru the undef, which will set source_dead to true } my $at_eof = ! $lines[-1]; # keep track of the undef pop @lines if $at_eof; # silence warnings # be eol agnostic s/\r\n?/\n/g for @lines; # make sure there are only one line elements for parse_lines @lines = split(/(?<=\n)/, join('', @lines)); # push the undef back after popping it to set source_dead to true push @lines, undef if $at_eof; $self->parse_lines(@lines); } delete($self->{'source_fh'}); # so it can be GC'd return $self; } #:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:. sub parse_from_file { # An emulation of Pod::Parser's interface, for the sake of Perldoc. # Basically just a wrapper around parse_file. my($self, $source, $to) = @_; $self = $self->new unless ref($self); # so we tolerate being a class method if(!defined $source) { $source = *STDIN{IO} } elsif(ref(\$source) eq 'GLOB') { # stet } elsif(ref($source) ) { # stet } elsif(!length $source or $source eq '-' or $source =~ m/^<&(?:STDIN|0)$/i ) { $source = *STDIN{IO}; } if(!defined $to) { $self->output_fh( *STDOUT{IO} ); } elsif(ref(\$to) eq 'GLOB') { $self->output_fh( $to ); } elsif(ref($to)) { $self->output_fh( $to ); } elsif(!length $to or $to eq '-' or $to =~ m/^>&?(?:STDOUT|1)$/i ) { $self->output_fh( *STDOUT{IO} ); } elsif($to =~ m/^>&(?:STDERR|2)$/i) { $self->output_fh( *STDERR{IO} ); } else { require Symbol; my $out_fh = Symbol::gensym(); DEBUG and print STDERR "Write-opening to $to\n"; open($out_fh, ">$to") or Carp::croak "Can't write-open $to: $!"; binmode($out_fh) if $self->can('write_with_binmode') and $self->write_with_binmode; $self->output_fh($out_fh); } return $self->parse_file($source); } #----------------------------------------------------------------------------- sub whine { #my($self,$line,$complaint) = @_; my $self = shift(@_); ++$self->{'errors_seen'}; if($self->{'no_whining'}) { DEBUG > 9 and print STDERR "Discarding complaint (at line $_[0]) $_[1]\n because no_whining is on.\n"; return; } push @{$self->{'all_errata'}{$_[0]}}, $_[1]; return $self->_complain_warn(@_) if $self->{'complain_stderr'}; return $self->_complain_errata(@_); } sub scream { # like whine, but not suppressible #my($self,$line,$complaint) = @_; my $self = shift(@_); ++$self->{'errors_seen'}; push @{$self->{'all_errata'}{$_[0]}}, $_[1]; return $self->_complain_warn(@_) if $self->{'complain_stderr'}; return $self->_complain_errata(@_); } sub _complain_warn { my($self,$line,$complaint) = @_; return printf STDERR "%s around line %s: %s\n", $self->{'source_filename'} || 'Pod input', $line, $complaint; } sub _complain_errata { my($self,$line,$complaint) = @_; if( $self->{'no_errata_section'} ) { DEBUG > 9 and print STDERR "Discarding erratum (at line $line) $complaint\n because no_errata_section is on.\n"; } else { DEBUG > 9 and print STDERR "Queuing erratum (at line $line) $complaint\n"; push @{$self->{'errata'}{$line}}, $complaint # for a report to be generated later! } return 1; } #@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ sub _get_initial_item_type { # A hack-wrapper here for when you have like "=over\n\n=item 456\n\n" my($self, $para) = @_; return $para->[1]{'~type'} if $para->[1]{'~type'}; return $para->[1]{'~type'} = 'text' if join("\n", @{$para}[2 .. $#$para]) =~ m/^\s*(\d+)\.?\s*$/s and $1 ne '1'; # Else fall thru to the general case: return $self->_get_item_type($para); } sub _get_item_type { # mutates the item!! my($self, $para) = @_; return $para->[1]{'~type'} if $para->[1]{'~type'}; # Otherwise we haven't yet been to this node. Maybe alter it... my $content = join "\n", @{$para}[2 .. $#$para]; if($content =~ m/^\s*\*\s*$/s or $content =~ m/^\s*$/s) { # Like: "=item *", "=item * ", "=item" splice @$para, 2; # so it ends up just being ['=item', { attrhash } ] $para->[1]{'~orig_content'} = $content; return $para->[1]{'~type'} = 'bullet'; } elsif($content =~ m/^\s*\*\s+(.+)/s) { # tolerance # Like: "=item * Foo bar baz"; $para->[1]{'~orig_content'} = $content; $para->[1]{'~_freaky_para_hack'} = $1; DEBUG > 2 and print STDERR " Tolerating $$para[2] as =item *\\n\\n$1\n"; splice @$para, 2; # so it ends up just being ['=item', { attrhash } ] return $para->[1]{'~type'} = 'bullet'; } elsif($content =~ m/^\s*(\d+)\.?\s*$/s) { # Like: "=item 1.", "=item 123412" $para->[1]{'~orig_content'} = $content; $para->[1]{'number'} = $1; # Yes, stores the number there! splice @$para, 2; # so it ends up just being ['=item', { attrhash } ] return $para->[1]{'~type'} = 'number'; } else { # It's anything else. return $para->[1]{'~type'} = 'text'; } } #----------------------------------------------------------------------------- sub _make_treelet { my $self = shift; # and ($para, $start_line) my $treelet; if(!@_) { return ['']; } if(ref $_[0] and ref $_[0][0] and $_[0][0][0] eq '~Top') { # Hack so we can pass in fake-o pre-cooked paragraphs: # just have the first line be a reference to a ['~Top', {}, ...] # We use this feechure in gen_errata and stuff. DEBUG and print STDERR "Applying precooked treelet hack to $_[0][0]\n"; $treelet = $_[0][0]; splice @$treelet, 0, 2; # lop the top off return $treelet; } else { $treelet = $self->_treelet_from_formatting_codes(@_); } if( $self->_remap_sequences($treelet) ) { $self->_treat_Zs($treelet); # Might as well nix these first $self->_treat_Ls($treelet); # L has to precede E and S $self->_treat_Es($treelet); $self->_treat_Ss($treelet); # S has to come after E $self->_wrap_up($treelet); # Nix X's and merge texties } else { DEBUG and print STDERR "Formatless treelet gets fast-tracked.\n"; # Very common case! } splice @$treelet, 0, 2; # lop the top off return $treelet; } #:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:. sub _wrap_up { my($self, @stack) = @_; my $nixx = $self->{'nix_X_codes'}; my $merge = $self->{'merge_text' }; return unless $nixx or $merge; DEBUG > 2 and print STDERR "\nStarting _wrap_up traversal.\n", $merge ? (" Merge mode on\n") : (), $nixx ? (" Nix-X mode on\n") : (), ; my($i, $treelet); while($treelet = shift @stack) { DEBUG > 3 and print STDERR " Considering children of this $treelet->[0] node...\n"; for($i = 2; $i < @$treelet; ++$i) { # iterate over children DEBUG > 3 and print STDERR " Considering child at $i ", pretty($treelet->[$i]), "\n"; if($nixx and ref $treelet->[$i] and $treelet->[$i][0] eq 'X') { DEBUG > 3 and print STDERR " Nixing X node at $i\n"; splice(@$treelet, $i, 1); # just nix this node (and its descendants) # no need to back-update the counter just yet redo; } elsif($merge and $i != 2 and # non-initial !ref $treelet->[$i] and !ref $treelet->[$i - 1] ) { DEBUG > 3 and print STDERR " Merging ", $i-1, ":[$treelet->[$i-1]] and $i\:[$treelet->[$i]]\n"; $treelet->[$i-1] .= ( splice(@$treelet, $i, 1) )[0]; DEBUG > 4 and print STDERR " Now: ", $i-1, ":[$treelet->[$i-1]]\n"; --$i; next; # since we just pulled the possibly last node out from under # ourselves, we can't just redo() } elsif( ref $treelet->[$i] ) { DEBUG > 4 and print STDERR " Enqueuing ", pretty($treelet->[$i]), " for traversal.\n"; push @stack, $treelet->[$i]; if($treelet->[$i][0] eq 'L') { my $thing; foreach my $attrname ('section', 'to') { if(defined($thing = $treelet->[$i][1]{$attrname}) and ref $thing) { unshift @stack, $thing; DEBUG > 4 and print STDERR " +Enqueuing ", pretty( $treelet->[$i][1]{$attrname} ), " as an attribute value to tweak.\n"; } } } } } } DEBUG > 2 and print STDERR "End of _wrap_up traversal.\n\n"; return; } #:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:. sub _remap_sequences { my($self,@stack) = @_; if(@stack == 1 and @{ $stack[0] } == 3 and !ref $stack[0][2]) { # VERY common case: abort it. DEBUG and print STDERR "Skipping _remap_sequences: formatless treelet.\n"; return 0; } my $map = ($self->{'accept_codes'} || die "NO accept_codes in $self?!?"); my $start_line = $stack[0][1]{'start_line'}; DEBUG > 2 and printf "\nAbout to start _remap_sequences on treelet from line %s.\n", $start_line || '[?]' ; DEBUG > 3 and print STDERR " Map: ", join('; ', map "$_=" . ( ref($map->{$_}) ? join(",", @{$map->{$_}}) : $map->{$_} ), sort keys %$map ), ("B~C~E~F~I~L~S~X~Z" eq join '~', sort keys %$map) ? " (all normal)\n" : "\n" ; # A recursive algorithm implemented iteratively! Whee! my($is, $was, $i, $treelet); # scratch while($treelet = shift @stack) { DEBUG > 3 and print STDERR " Considering children of this $treelet->[0] node...\n"; for($i = 2; $i < @$treelet; ++$i) { # iterate over children next unless ref $treelet->[$i]; # text nodes are uninteresting DEBUG > 4 and print STDERR " Noting child $i : $treelet->[$i][0]<...>\n"; $is = $treelet->[$i][0] = $map->{ $was = $treelet->[$i][0] }; if( DEBUG > 3 ) { if(!defined $is) { print STDERR " Code $was<> is UNKNOWN!\n"; } elsif($is eq $was) { DEBUG > 4 and print STDERR " Code $was<> stays the same.\n"; } else { print STDERR " Code $was<> maps to ", ref($is) ? ( "tags ", map("$_<", @$is), '...', map('>', @$is), "\n" ) : "tag $is<...>.\n"; } } if(!defined $is) { $self->whine($start_line, "Deleting unknown formatting code $was<>"); $is = $treelet->[$i][0] = '1'; # But saving the children! # I could also insert a leading "$was<" and tailing ">" as # children of this node, but something about that seems icky. } if(ref $is) { my @dynasty = @$is; DEBUG > 4 and print STDERR " Renaming $was node to $dynasty[-1]\n"; $treelet->[$i][0] = pop @dynasty; my $nugget; while(@dynasty) { DEBUG > 4 and printf " Grafting a new %s node between %s and %s\n", $dynasty[-1], $treelet->[0], $treelet->[$i][0], ; #$nugget = ; splice @$treelet, $i, 1, [pop(@dynasty), {}, $treelet->[$i]]; # relace node with a new parent } } elsif($is eq '0') { splice(@$treelet, $i, 1); # just nix this node (and its descendants) --$i; # back-update the counter } elsif($is eq '1') { splice(@$treelet, $i, 1 # replace this node with its children! => splice @{ $treelet->[$i] },2 # (not catching its first two (non-child) items) ); --$i; # back up for new stuff } else { # otherwise it's unremarkable unshift @stack, $treelet->[$i]; # just recurse } } } DEBUG > 2 and print STDERR "End of _remap_sequences traversal.\n\n"; if(@_ == 2 and @{ $_[1] } == 3 and !ref $_[1][2]) { DEBUG and print STDERR "Noting that the treelet is now formatless.\n"; return 0; } return 1; } # . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . sub _ponder_extend { # "Go to an extreme, move back to a more comfortable place" # -- /Oblique Strategies/, Brian Eno and Peter Schmidt my($self, $para) = @_; my $content = join ' ', splice @$para, 2; $content =~ s/^\s+//s; $content =~ s/\s+$//s; DEBUG > 2 and print STDERR "Ogling extensor: =extend $content\n"; if($content =~ m/^ (\S+) # 1 : new item \s+ (\S+) # 2 : fallback(s) (?:\s+(\S+))? # 3 : element name(s) \s* $ /xs ) { my $new_letter = $1; my $fallbacks_one = $2; my $elements_one; $elements_one = defined($3) ? $3 : $1; DEBUG > 2 and print STDERR "Extensor has good syntax.\n"; unless($new_letter =~ m/^[A-Z]$/s or $new_letter) { DEBUG > 2 and print STDERR " $new_letter isn't a valid thing to entend.\n"; $self->whine( $para->[1]{'start_line'}, "You can extend only formatting codes A-Z, not like \"$new_letter\"" ); return; } if(grep $new_letter eq $_, @Known_formatting_codes) { DEBUG > 2 and print STDERR " $new_letter isn't a good thing to extend, because known.\n"; $self->whine( $para->[1]{'start_line'}, "You can't extend an established code like \"$new_letter\"" ); #TODO: or allow if last bit is same? return; } unless($fallbacks_one =~ m/^[A-Z](,[A-Z])*$/s # like "B", "M,I", etc. or $fallbacks_one eq '0' or $fallbacks_one eq '1' ) { $self->whine( $para->[1]{'start_line'}, "Format for second =extend parameter must be like" . " M or 1 or 0 or M,N or M,N,O but you have it like " . $fallbacks_one ); return; } unless($elements_one =~ m/^[^ ,]+(,[^ ,]+)*$/s) { # like "B", "M,I", etc. $self->whine( $para->[1]{'start_line'}, "Format for third =extend parameter: like foo or bar,Baz,qu:ux but not like " . $elements_one ); return; } my @fallbacks = split ',', $fallbacks_one, -1; my @elements = split ',', $elements_one, -1; foreach my $f (@fallbacks) { next if exists $Known_formatting_codes{$f} or $f eq '0' or $f eq '1'; DEBUG > 2 and print STDERR " Can't fall back on unknown code $f\n"; $self->whine( $para->[1]{'start_line'}, "Can't use unknown formatting code '$f' as a fallback for '$new_letter'" ); return; } DEBUG > 3 and printf STDERR "Extensor: Fallbacks <%s> Elements <%s>.\n", @fallbacks, @elements; my $canonical_form; foreach my $e (@elements) { if(exists $self->{'accept_codes'}{$e}) { DEBUG > 1 and print STDERR " Mapping '$new_letter' to known extension '$e'\n"; $canonical_form = $e; last; # first acceptable elementname wins! } else { DEBUG > 1 and print STDERR " Can't map '$new_letter' to unknown extension '$e'\n"; } } if( defined $canonical_form ) { # We found a good N => elementname mapping $self->{'accept_codes'}{$new_letter} = $canonical_form; DEBUG > 2 and print "Extensor maps $new_letter => known element $canonical_form.\n"; } else { # We have to use the fallback(s), which might be '0', or '1'. $self->{'accept_codes'}{$new_letter} = (@fallbacks == 1) ? $fallbacks[0] : \@fallbacks; DEBUG > 2 and print "Extensor maps $new_letter => fallbacks @fallbacks.\n"; } } else { DEBUG > 2 and print STDERR "Extensor has bad syntax.\n"; $self->whine( $para->[1]{'start_line'}, "Unknown =extend syntax: $content" ) } return; } #:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:. sub _treat_Zs { # Nix Z<...>'s my($self,@stack) = @_; my($i, $treelet); my $start_line = $stack[0][1]{'start_line'}; # A recursive algorithm implemented iteratively! Whee! while($treelet = shift @stack) { for($i = 2; $i < @$treelet; ++$i) { # iterate over children next unless ref $treelet->[$i]; # text nodes are uninteresting unless($treelet->[$i][0] eq 'Z') { unshift @stack, $treelet->[$i]; # recurse next; } DEBUG > 1 and print STDERR "Nixing Z node @{$treelet->[$i]}\n"; # bitch UNLESS it's empty unless( @{$treelet->[$i]} == 2 or (@{$treelet->[$i]} == 3 and $treelet->[$i][2] eq '') ) { $self->whine( $start_line, "A non-empty Z<>" ); } # but kill it anyway splice(@$treelet, $i, 1); # thereby just nix this node. --$i; } } return; } # . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . # Quoting perlpodspec: # In parsing an L<...> code, Pod parsers must distinguish at least four # attributes: ############# Not used. Expressed via the element children plus ############# the value of the "content-implicit" flag. # First: # The link-text. If there is none, this must be undef. (E.g., in "L", the link-text is "Perl Functions". In # "L" and even "L<|Time::HiRes>", there is no link text. Note # that link text may contain formatting.) # ############# The element children # Second: # The possibly inferred link-text -- i.e., if there was no real link text, # then this is the text that we'll infer in its place. (E.g., for # "L", the inferred link text is "Getopt::Std".) # ############# The "to" attribute (which might be text, or a treelet) # Third: # The name or URL, or undef if none. (E.g., in "L", the name -- also sometimes called the page -- is # "perlfunc". In "L", the name is undef.) # ############# The "section" attribute (which might be next, or a treelet) # Fourth: # The section (AKA "item" in older perlpods), or undef if none. E.g., in # Getopt::Std/DESCRIPTION, "DESCRIPTION" is the section. (Note that this # is not the same as a manpage section like the "5" in "man 5 crontab". # "Section Foo" in the Pod sense means the part of the text that's # introduced by the heading or item whose text is "Foo".) # # Pod parsers may also note additional attributes including: # ############# The "type" attribute. # Fifth: # A flag for whether item 3 (if present) is a URL (like # "http://lists.perl.org" is), in which case there should be no section # attribute; a Pod name (like "perldoc" and "Getopt::Std" are); or # possibly a man page name (like "crontab(5)" is). # ############# The "raw" attribute that is already there. # Sixth: # The raw original L<...> content, before text is split on "|", "/", etc, # and before E<...> codes are expanded. # For L<...> codes without a "name|" part, only E<...> and Z<> codes may # occur -- no other formatting codes. That is, authors should not use # "L>". # # Note, however, that formatting codes and Z<>'s can occur in any and all # parts of an L<...> (i.e., in name, section, text, and url). sub _treat_Ls { # Process our dear dear friends, the L<...> sequences # L # L or L # L or L or L<"sec"> # L # L or L # L or L or L # L # L my($self,@stack) = @_; my($i, $treelet); my $start_line = $stack[0][1]{'start_line'}; # A recursive algorithm implemented iteratively! Whee! while($treelet = shift @stack) { for(my $i = 2; $i < @$treelet; ++$i) { # iterate over children of current tree node next unless ref $treelet->[$i]; # text nodes are uninteresting unless($treelet->[$i][0] eq 'L') { unshift @stack, $treelet->[$i]; # recurse next; } # By here, $treelet->[$i] is definitely an L node my $ell = $treelet->[$i]; DEBUG > 1 and print STDERR "Ogling L node $ell\n"; # bitch if it's empty if( @{$ell} == 2 or (@{$ell} == 3 and $ell->[2] eq '') ) { $self->whine( $start_line, "An empty L<>" ); $treelet->[$i] = 'L<>'; # just make it a text node next; # and move on } if( (! ref $ell->[2] && $ell->[2] =~ /\A\s/) ||(! ref $ell->[-1] && $ell->[-1] =~ /\s\z/) ) { $self->whine( $start_line, "L<> starts or ends with whitespace" ); } # Catch URLs: # there are a number of possible cases: # 1) text node containing url: http://foo.com # -> [ 'http://foo.com' ] # 2) text node containing url and text: foo|http://foo.com # -> [ 'foo|http://foo.com' ] # 3) text node containing url start: mailto:xEfoo.com # -> [ 'mailto:x', [ E ... ], 'foo.com' ] # 4) text node containing url start and text: foo|mailto:xEfoo.com # -> [ 'foo|mailto:x', [ E ... ], 'foo.com' ] # 5) other nodes containing text and url start: OE<39>Malley|http://foo.com # -> [ 'O', [ E ... ], 'Malley', '|http://foo.com' ] # ... etc. # anything before the url is part of the text. # anything after it is part of the url. # the url text node itself may contain parts of both. if (my ($url_index, $text_part, $url_part) = # grep is no good here; we want to bail out immediately so that we can # use $1, $2, etc. without having to do the match twice. sub { for (2..$#$ell) { next if ref $ell->[$_]; next unless $ell->[$_] =~ m/^(?:([^|]*)\|)?(\w+:[^:\s]\S*)$/s; return ($_, $1, $2); } return; }->() ) { $ell->[1]{'type'} = 'url'; my @text = @{$ell}[2..$url_index-1]; push @text, $text_part if defined $text_part; my @url = @{$ell}[$url_index+1..$#$ell]; unshift @url, $url_part; unless (@text) { $ell->[1]{'content-implicit'} = 'yes'; @text = @url; } $ell->[1]{to} = Pod::Simple::LinkSection->new( @url == 1 ? $url[0] : [ '', {}, @url ], ); splice @$ell, 2, $#$ell, @text; next; } # Catch some very simple and/or common cases if(@{$ell} == 3 and ! ref $ell->[2]) { my $it = $ell->[2]; if($it =~ m{^[^/|]+[(][-a-zA-Z0-9]+[)]$}s) { # man sections # Hopefully neither too broad nor too restrictive a RE DEBUG > 1 and print STDERR "Catching \"$it\" as manpage link.\n"; $ell->[1]{'type'} = 'man'; # This's the only place where man links can get made. $ell->[1]{'content-implicit'} = 'yes'; $ell->[1]{'to' } = Pod::Simple::LinkSection->new( $it ); # treelet! next; } if($it =~ m/^[^\/\|,\$\%\@\ \"\<\>\:\#\&\*\{\}\[\]\(\)]+(\:\:[^\/\|,\$\%\@\ \"\<\>\:\#\&\*\{\}\[\]\(\)]+)*$/s) { # Extremely forgiving idea of what constitutes a bare # modulename link like L or even L DEBUG > 1 and print STDERR "Catching \"$it\" as ho-hum L link.\n"; $ell->[1]{'type'} = 'pod'; $ell->[1]{'content-implicit'} = 'yes'; $ell->[1]{'to' } = Pod::Simple::LinkSection->new( $it ); # treelet! next; } # else fall thru... } # ...Uhoh, here's the real L<...> parsing stuff... # "With the ill behavior, with the ill behavior, with the ill behavior..." DEBUG > 1 and print STDERR "Running a real parse on this non-trivial L\n"; my $link_text; # set to an arrayref if found my @ell_content = @$ell; splice @ell_content,0,2; # Knock off the 'L' and {} bits DEBUG > 3 and print STDERR " Ell content to start: ", pretty(@ell_content), "\n"; # Look for the "|" -- only in CHILDREN (not all underlings!) # Like L DEBUG > 3 and print STDERR " Peering at L content for a '|' ...\n"; for(my $j = 0; $j < @ell_content; ++$j) { next if ref $ell_content[$j]; DEBUG > 3 and print STDERR " Peering at L-content text bit \"$ell_content[$j]\" for a '|'.\n"; if($ell_content[$j] =~ m/^([^\|]*)\|(.*)$/s) { my @link_text = ($1); # might be 0-length $ell_content[$j] = $2; # might be 0-length DEBUG > 3 and print STDERR " FOUND a '|' in it. Splitting into [$1] + [$2]\n"; if ($link_text[0] =~ m{[|/]}) { $self->whine( $start_line, "alternative text '$link_text[0]' contains non-escaped | or /" ); } unshift @link_text, splice @ell_content, 0, $j; # leaving only things at J and after @ell_content = grep ref($_)||length($_), @ell_content ; $link_text = [grep ref($_)||length($_), @link_text ]; DEBUG > 3 and printf " So link text is %s\n and remaining ell content is %s\n", pretty($link_text), pretty(@ell_content); last; } } # Now look for the "/" -- only in CHILDREN (not all underlings!) # And afterward, anything left in @ell_content will be the raw name # Like L my $section_name; # set to arrayref if found DEBUG > 3 and print STDERR " Peering at L-content for a '/' ...\n"; for(my $j = 0; $j < @ell_content; ++$j) { next if ref $ell_content[$j]; DEBUG > 3 and print STDERR " Peering at L-content text bit \"$ell_content[$j]\" for a '/'.\n"; if($ell_content[$j] =~ m/^([^\/]*)\/(.*)$/s) { my @section_name = ($2); # might be 0-length $ell_content[$j] = $1; # might be 0-length DEBUG > 3 and print STDERR " FOUND a '/' in it.", " Splitting to page [...$1] + section [$2...]\n"; push @section_name, splice @ell_content, 1+$j; # leaving only things before and including J @ell_content = grep ref($_)||length($_), @ell_content ; @section_name = grep ref($_)||length($_), @section_name ; # Turn L<.../"foo"> into L<.../foo> if(@section_name and !ref($section_name[0]) and !ref($section_name[-1]) and $section_name[ 0] =~ m/^\"/s and $section_name[-1] =~ m/\"$/s and !( # catch weird degenerate case of L<"> ! @section_name == 1 and $section_name[0] eq '"' ) ) { $section_name[ 0] =~ s/^\"//s; $section_name[-1] =~ s/\"$//s; DEBUG > 3 and print STDERR " Quotes removed: ", pretty(@section_name), "\n"; } else { DEBUG > 3 and print STDERR " No need to remove quotes in ", pretty(@section_name), "\n"; } $section_name = \@section_name; last; } } # Turn L<"Foo Bar"> into L if(!$section_name and @ell_content and !ref($ell_content[0]) and !ref($ell_content[-1]) and $ell_content[ 0] =~ m/^\"/s and $ell_content[-1] =~ m/\"$/s and !( # catch weird degenerate case of L<"> ! @ell_content == 1 and $ell_content[0] eq '"' ) ) { $section_name = [splice @ell_content]; $section_name->[ 0] =~ s/^\"//s; $section_name->[-1] =~ s/\"$//s; } # Turn L into L. if(!$section_name and !$link_text and @ell_content and grep !ref($_) && m/ /s, @ell_content ) { $section_name = [splice @ell_content]; # That's support for the now-deprecated syntax. # (Maybe generate a warning eventually?) # Note that it deliberately won't work on L<...|Foo Bar> } # Now make up the link_text # L -> L # L -> L<"Bar"|Bar> # L -> L<"Bar" in Foo/Foo> unless($link_text) { $ell->[1]{'content-implicit'} = 'yes'; $link_text = []; push @$link_text, '"', @$section_name, '"' if $section_name; if(@ell_content) { $link_text->[-1] .= ' in ' if $section_name; push @$link_text, @ell_content; } } # And the E resolver will have to deal with all our treeletty things: if(@ell_content == 1 and !ref($ell_content[0]) and $ell_content[0] =~ m{^[^/]+[(][-a-zA-Z0-9]+[)]$}s ) { $ell->[1]{'type'} = 'man'; DEBUG > 3 and print STDERR "Considering this ($ell_content[0]) a man link.\n"; } else { $ell->[1]{'type'} = 'pod'; DEBUG > 3 and print STDERR "Considering this a pod link (not man or url).\n"; } if( defined $section_name ) { $ell->[1]{'section'} = Pod::Simple::LinkSection->new( ['', {}, @$section_name] ); DEBUG > 3 and print STDERR "L-section content: ", pretty($ell->[1]{'section'}), "\n"; } if( @ell_content ) { $ell->[1]{'to'} = Pod::Simple::LinkSection->new( ['', {}, @ell_content] ); DEBUG > 3 and print STDERR "L-to content: ", pretty($ell->[1]{'to'}), "\n"; } # And update children to be the link-text: @$ell = (@$ell[0,1], defined($link_text) ? splice(@$link_text) : ''); DEBUG > 2 and print STDERR "End of L-parsing for this node $treelet->[$i]\n"; unshift @stack, $treelet->[$i]; # might as well recurse } } return; } # . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . sub _treat_Es { my($self,@stack) = @_; my($i, $treelet, $content, $replacer, $charnum); my $start_line = $stack[0][1]{'start_line'}; # A recursive algorithm implemented iteratively! Whee! # Has frightening side effects on L nodes' attributes. #my @ells_to_tweak; while($treelet = shift @stack) { for(my $i = 2; $i < @$treelet; ++$i) { # iterate over children next unless ref $treelet->[$i]; # text nodes are uninteresting if($treelet->[$i][0] eq 'L') { # SPECIAL STUFF for semi-processed L<>'s my $thing; foreach my $attrname ('section', 'to') { if(defined($thing = $treelet->[$i][1]{$attrname}) and ref $thing) { unshift @stack, $thing; DEBUG > 2 and print STDERR " Enqueuing ", pretty( $treelet->[$i][1]{$attrname} ), " as an attribute value to tweak.\n"; } } unshift @stack, $treelet->[$i]; # recurse next; } elsif($treelet->[$i][0] ne 'E') { unshift @stack, $treelet->[$i]; # recurse next; } DEBUG > 1 and print STDERR "Ogling E node ", pretty($treelet->[$i]), "\n"; # bitch if it's empty if( @{$treelet->[$i]} == 2 or (@{$treelet->[$i]} == 3 and $treelet->[$i][2] eq '') ) { $self->whine( $start_line, "An empty E<>" ); $treelet->[$i] = 'E<>'; # splice in a literal next; } # bitch if content is weird unless(@{$treelet->[$i]} == 3 and !ref($content = $treelet->[$i][2])) { $self->whine( $start_line, "An E<...> surrounding strange content" ); $replacer = $treelet->[$i]; # scratch splice(@$treelet, $i, 1, # fake out a literal 'E<', splice(@$replacer,2), # promote its content '>' ); # Don't need to do --$i, as the 'E<' we just added isn't interesting. next; } DEBUG > 1 and print STDERR "Ogling E<$content>\n"; # XXX E<>'s contents *should* be a valid char in the scope of the current # =encoding directive. Defaults to iso-8859-1, I believe. Fix this in the # future sometime. $charnum = Pod::Escapes::e2charnum($content); DEBUG > 1 and print STDERR " Considering E<$content> with char ", defined($charnum) ? $charnum : "undef", ".\n"; if(!defined( $charnum )) { DEBUG > 1 and print STDERR "I don't know how to deal with E<$content>.\n"; $self->whine( $start_line, "Unknown E content in E<$content>" ); $replacer = "E<$content>"; # better than nothing } elsif($charnum >= 255 and !UNICODE) { $replacer = ASCII ? "\xA4" : "?"; DEBUG > 1 and print STDERR "This Perl version can't handle ", "E<$content> (chr $charnum), so replacing with $replacer\n"; } else { $replacer = Pod::Escapes::e2char($content); DEBUG > 1 and print STDERR " Replacing E<$content> with $replacer\n"; } splice(@$treelet, $i, 1, $replacer); # no need to back up $i, tho } } return; } # . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . sub _treat_Ss { my($self,$treelet) = @_; _change_S_to_nbsp($treelet,0) if $self->{'nbsp_for_S'}; # TODO: or a change_nbsp_to_S # Normalizing nbsp's to S is harder: for each text node, make S content # out of anything matching m/([^ \xA0]*(?:\xA0+[^ \xA0]*)+)/ return; } sub _change_S_to_nbsp { # a recursive function # Sanely assumes that the top node in the excursion won't be an S node. my($treelet, $in_s) = @_; my $is_s = ('S' eq $treelet->[0]); $in_s ||= $is_s; # So in_s is on either by this being an S element, # or by an ancestor being an S element. for(my $i = 2; $i < @$treelet; ++$i) { if(ref $treelet->[$i]) { if( _change_S_to_nbsp( $treelet->[$i], $in_s ) ) { my $to_pull_up = $treelet->[$i]; splice @$to_pull_up,0,2; # ...leaving just its content splice @$treelet, $i, 1, @$to_pull_up; # Pull up content $i += @$to_pull_up - 1; # Make $i skip the pulled-up stuff } } else { $treelet->[$i] =~ s/\s/$Pod::Simple::nbsp/g if $in_s; # Note that if you apply nbsp_for_S to text, and so turn # "foo S quux" into "foo bar faz quux", you # end up with something that fails to say "and don't hyphenate # any part of 'bar baz'". However, hyphenation is such a vexing # problem anyway, that most Pod renderers just don't render it # at all. But if you do want to implement hyphenation, I guess # that you'd better have nbsp_for_S off. } } return $is_s; } #----------------------------------------------------------------------------- sub _accessorize { # A simple-minded method-maker no strict 'refs'; foreach my $attrname (@_) { next if $attrname =~ m/::/; # a hack *{caller() . '::' . $attrname} = sub { use strict; $Carp::CarpLevel = 1, Carp::croak( "Accessor usage: \$obj->$attrname() or \$obj->$attrname(\$new_value)" ) unless (@_ == 1 or @_ == 2) and ref $_[0]; (@_ == 1) ? $_[0]->{$attrname} : ($_[0]->{$attrname} = $_[1]); }; } # Ya know, they say accessories make the ensemble! return; } # . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . # . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . #============================================================================= sub filter { my($class, $source) = @_; my $new = $class->new; $new->output_fh(*STDOUT{IO}); if(ref($source || '') eq 'SCALAR') { $new->parse_string_document( $$source ); } elsif(ref($source)) { # it's a file handle $new->parse_file($source); } else { # it's a filename $new->parse_file($source); } return $new; } #----------------------------------------------------------------------------- sub _out { # For use in testing: Class->_out($source) # returns the transformation of $source my $class = shift(@_); my $mutor = shift(@_) if @_ and ref($_[0] || '') eq 'CODE'; DEBUG and print STDERR "\n\n", '#' x 76, "\nAbout to parse source: {{\n$_[0]\n}}\n\n"; my $parser = ref $class && $class->isa(__PACKAGE__) ? $class : $class->new; $parser->hide_line_numbers(1); my $out = ''; $parser->output_string( \$out ); DEBUG and print STDERR " _out to ", \$out, "\n"; $mutor->($parser) if $mutor; $parser->parse_string_document( $_[0] ); # use Data::Dumper; print STDERR Dumper($parser), "\n"; return $out; } sub _duo { # For use in testing: Class->_duo($source1, $source2) # returns the parse trees of $source1 and $source2. # Good in things like: &ok( Class->duo(... , ...) ); my $class = shift(@_); Carp::croak "But $class->_duo is useful only in list context!" unless wantarray; my $mutor = shift(@_) if @_ and ref($_[0] || '') eq 'CODE'; Carp::croak "But $class->_duo takes two parameters, not: @_" unless @_ == 2; my(@out); while( @_ ) { my $parser = $class->new; push @out, ''; $parser->output_string( \( $out[-1] ) ); DEBUG and print STDERR " _duo out to ", $parser->output_string(), " = $parser->{'output_string'}\n"; $parser->hide_line_numbers(1); $mutor->($parser) if $mutor; $parser->parse_string_document( shift( @_ ) ); # use Data::Dumper; print STDERR Dumper($parser), "\n"; } return @out; } #----------------------------------------------------------------------------- 1; __END__ TODO: A start_formatting_code and end_formatting_code methods, which in the base class call start_L, end_L, start_C, end_C, etc., if they are defined. have the POD FORMATTING ERRORS section note the localtime, and the version of Pod::Simple. option to delete all Es? option to scream if under-0x20 literals are found in the input, or under-E<32> E codes are found in the tree. And ditto \x7f-\x9f Option to turn highbit characters into their compromised form? (applies to E parsing too) TODO: BOM/encoding things. TODO: ascii-compat things in the XML classes? ############################################################################# # Pod/ParseUtils.pm -- helpers for POD parsing and conversion # # Copyright (C) 1999-2000 by Marek Rouchal. All rights reserved. # This file is part of "PodParser". PodParser is free software; # you can redistribute it and/or modify it under the same terms # as Perl itself. ############################################################################# package Pod::ParseUtils; use strict; use vars qw($VERSION); $VERSION = '1.63'; ## Current version of this package require 5.005; ## requires this Perl version or later #----------------------------------------------------------------------------- # Pod::List # # class to hold POD list info (=over, =item, =back) #----------------------------------------------------------------------------- package Pod::List; use Carp; sub new { my $this = shift; my $class = ref($this) || $this; my %params = @_; my $self = {%params}; bless $self, $class; $self->initialize(); return $self; } sub initialize { my $self = shift; $self->{-file} ||= 'unknown'; $self->{-start} ||= 'unknown'; $self->{-indent} ||= 4; # perlpod: "should be the default" $self->{_items} = []; $self->{-type} ||= ''; } # The POD file name the list appears in sub file { return (@_ > 1) ? ($_[0]->{-file} = $_[1]) : $_[0]->{-file}; } # The line in the file the node appears sub start { return (@_ > 1) ? ($_[0]->{-start} = $_[1]) : $_[0]->{-start}; } # indent level sub indent { return (@_ > 1) ? ($_[0]->{-indent} = $_[1]) : $_[0]->{-indent}; } # The type of the list (UL, OL, ...) sub type { return (@_ > 1) ? ($_[0]->{-type} = $_[1]) : $_[0]->{-type}; } # The regular expression to simplify the items sub rx { return (@_ > 1) ? ($_[0]->{-rx} = $_[1]) : $_[0]->{-rx}; } # The individual =items of this list sub item { my ($self,$item) = @_; if(defined $item) { push(@{$self->{_items}}, $item); return $item; } else { return @{$self->{_items}}; } } # possibility for parsers/translators to store information about the # lists's parent object sub parent { return (@_ > 1) ? ($_[0]->{-parent} = $_[1]) : $_[0]->{-parent}; } # possibility for parsers/translators to store information about the # list's object sub tag { return (@_ > 1) ? ($_[0]->{-tag} = $_[1]) : $_[0]->{-tag}; } #----------------------------------------------------------------------------- # Pod::Hyperlink # # class to manipulate POD hyperlinks (L<>) #----------------------------------------------------------------------------- package Pod::Hyperlink; use Carp; sub new { my $this = shift; my $class = ref($this) || $this; my $self = +{}; bless $self, $class; $self->initialize(); if(defined $_[0]) { if(ref($_[0])) { # called with a list of parameters %$self = %{$_[0]}; $self->_construct_text(); } else { # called with L<> contents return unless($self->parse($_[0])); } } return $self; } sub initialize { my $self = shift; $self->{-line} ||= 'undef'; $self->{-file} ||= 'undef'; $self->{-page} ||= ''; $self->{-node} ||= ''; $self->{-alttext} ||= ''; $self->{-type} ||= 'undef'; $self->{_warnings} = []; } sub parse { my $self = shift; local($_) = $_[0]; # syntax check the link and extract destination my ($alttext,$page,$node,$type,$quoted) = (undef,'','','',0); $self->{_warnings} = []; # collapse newlines with whitespace s/\s*\n+\s*/ /g; # strip leading/trailing whitespace if(s/^[\s\n]+//) { $self->warning('ignoring leading whitespace in link'); } if(s/[\s\n]+$//) { $self->warning('ignoring trailing whitespace in link'); } unless(length($_)) { _invalid_link('empty link'); return; } ## Check for different possibilities. This is tedious and error-prone # we match all possibilities (alttext, page, section/item) #warn "DEBUG: link=$_\n"; # only page # problem: a lot of people use (), or (1) or the like to indicate # man page sections. But this collides with L that is supposed # to point to an internal function... my $page_rx = '[\w.-]+(?:::[\w.-]+)*(?:[(](?:\d\w*|)[)]|)'; # page name only if(/^($page_rx)$/o) { $page = $1; $type = 'page'; } # alttext, page and "section" elsif(m{^(.*?)\s*[|]\s*($page_rx)\s*/\s*"(.+)"$}o) { ($alttext, $page, $node) = ($1, $2, $3); $type = 'section'; $quoted = 1; #... therefore | and / are allowed } # alttext and page elsif(/^(.*?)\s*[|]\s*($page_rx)$/o) { ($alttext, $page) = ($1, $2); $type = 'page'; } # alttext and "section" elsif(m{^(.*?)\s*[|]\s*(?:/\s*|)"(.+)"$}) { ($alttext, $node) = ($1,$2); $type = 'section'; $quoted = 1; } # page and "section" elsif(m{^($page_rx)\s*/\s*"(.+)"$}o) { ($page, $node) = ($1, $2); $type = 'section'; $quoted = 1; } # page and item elsif(m{^($page_rx)\s*/\s*(.+)$}o) { ($page, $node) = ($1, $2); $type = 'item'; } # only "section" elsif(m{^/?"(.+)"$}) { $node = $1; $type = 'section'; $quoted = 1; } # only item elsif(m{^\s*/(.+)$}) { $node = $1; $type = 'item'; } # non-standard: Hyperlink with alt-text - doesn't remove protocol prefix, maybe it should? elsif(/^ \s* (.*?) \s* [|] \s* (\w+:[^:\s] [^\s|]*?) \s* $/ix) { ($alttext,$node) = ($1,$2); $type = 'hyperlink'; } # non-standard: Hyperlink elsif(/^(\w+:[^:\s]\S*)$/i) { $node = $1; $type = 'hyperlink'; } # alttext, page and item elsif(m{^(.*?)\s*[|]\s*($page_rx)\s*/\s*(.+)$}o) { ($alttext, $page, $node) = ($1, $2, $3); $type = 'item'; } # alttext and item elsif(m{^(.*?)\s*[|]\s*/(.+)$}) { ($alttext, $node) = ($1,$2); } # must be an item or a "malformed" section (without "") else { $node = $_; $type = 'item'; } # collapse whitespace in nodes $node =~ s/\s+/ /gs; # empty alternative text expands to node name if(defined $alttext) { if(!length($alttext)) { $alttext = $node || $page; } } else { $alttext = ''; } if($page =~ /[(]\w*[)]$/) { $self->warning("(section) in '$page' deprecated"); } if(!$quoted && $node =~ m{[|/]} && $type ne 'hyperlink') { $self->warning("node '$node' contains non-escaped | or /"); } if($alttext =~ m{[|/]}) { $self->warning("alternative text '$node' contains non-escaped | or /"); } $self->{-page} = $page; $self->{-node} = $node; $self->{-alttext} = $alttext; #warn "DEBUG: page=$page section=$section item=$item alttext=$alttext\n"; $self->{-type} = $type; $self->_construct_text(); 1; } sub _construct_text { my $self = shift; my $alttext = $self->alttext(); my $type = $self->type(); my $section = $self->node(); my $page = $self->page(); my $page_ext = ''; $page =~ s/([(]\w*[)])$// && ($page_ext = $1); if($alttext) { $self->{_text} = $alttext; } elsif($type eq 'hyperlink') { $self->{_text} = $section; } else { $self->{_text} = ($section || '') . (($page && $section) ? ' in ' : '') . "$page$page_ext"; } # for being marked up later # use the non-standard markers P<> and Q<>, so that the resulting # text can be parsed by the translators. It's their job to put # the correct hypertext around the linktext if($alttext) { $self->{_markup} = "Q<$alttext>"; } elsif($type eq 'hyperlink') { $self->{_markup} = "Q<$section>"; } else { $self->{_markup} = (!$section ? '' : "Q<$section>") . ($page ? ($section ? ' in ':'') . "P<$page>$page_ext" : ''); } } #' retrieve/set markuped text sub markup { return (@_ > 1) ? ($_[0]->{_markup} = $_[1]) : $_[0]->{_markup}; } # The complete link's text sub text { return $_[0]->{_text}; } # Set/retrieve warnings sub warning { my $self = shift; if(@_) { push(@{$self->{_warnings}}, @_); return @_; } return @{$self->{_warnings}}; } # The line in the file the link appears sub line { return (@_ > 1) ? ($_[0]->{-line} = $_[1]) : $_[0]->{-line}; } # The POD file name the link appears in sub file { return (@_ > 1) ? ($_[0]->{-file} = $_[1]) : $_[0]->{-file}; } # The POD page the link appears on sub page { if (@_ > 1) { $_[0]->{-page} = $_[1]; $_[0]->_construct_text(); } return $_[0]->{-page}; } # The link destination sub node { if (@_ > 1) { $_[0]->{-node} = $_[1]; $_[0]->_construct_text(); } return $_[0]->{-node}; } # Potential alternative text sub alttext { if (@_ > 1) { $_[0]->{-alttext} = $_[1]; $_[0]->_construct_text(); } return $_[0]->{-alttext}; } # The type: item or headn sub type { return (@_ > 1) ? ($_[0]->{-type} = $_[1]) : $_[0]->{-type}; } # The link itself sub link { my $self = shift; my $link = $self->page() || ''; if($self->node()) { my $node = $self->node(); $node =~ s/\|/E/g; $node =~ s{/}{E}g; if($self->type() eq 'section') { $link .= ($link ? '/' : '') . '"' . $node . '"'; } elsif($self->type() eq 'hyperlink') { $link = $self->node(); } else { # item $link .= '/' . $node; } } if($self->alttext()) { my $text = $self->alttext(); $text =~ s/\|/E/g; $text =~ s{/}{E}g; $link = "$text|$link"; } return $link; } sub _invalid_link { my ($msg) = @_; # this sets @_ #eval { die "$msg\n" }; #chomp $@; $@ = $msg; # this seems to work, too! return; } #----------------------------------------------------------------------------- # Pod::Cache # # class to hold POD page details #----------------------------------------------------------------------------- package Pod::Cache; sub new { my $this = shift; my $class = ref($this) || $this; my $self = []; bless $self, $class; return $self; } sub item { my ($self,%param) = @_; if(%param) { my $item = Pod::Cache::Item->new(%param); push(@$self, $item); return $item; } else { return @{$self}; } } sub find_page { my ($self,$page) = @_; foreach(@$self) { if($_->page() eq $page) { return $_; } } return; } package Pod::Cache::Item; sub new { my $this = shift; my $class = ref($this) || $this; my %params = @_; my $self = {%params}; bless $self, $class; $self->initialize(); return $self; } sub initialize { my $self = shift; $self->{-nodes} = [] unless(defined $self->{-nodes}); } # The POD page sub page { return (@_ > 1) ? ($_[0]->{-page} = $_[1]) : $_[0]->{-page}; } # The POD description, taken out of NAME if present sub description { return (@_ > 1) ? ($_[0]->{-description} = $_[1]) : $_[0]->{-description}; } # The file path sub path { return (@_ > 1) ? ($_[0]->{-path} = $_[1]) : $_[0]->{-path}; } # The POD file name sub file { return (@_ > 1) ? ($_[0]->{-file} = $_[1]) : $_[0]->{-file}; } # The POD nodes sub nodes { my ($self,@nodes) = @_; if(@nodes) { push(@{$self->{-nodes}}, @nodes); return @nodes; } else { return @{$self->{-nodes}}; } } sub find_node { my ($self,$node) = @_; my @search; push(@search, @{$self->{-nodes}}) if($self->{-nodes}); push(@search, @{$self->{-idx}}) if($self->{-idx}); foreach(@search) { if($_->[0] eq $node) { return $_->[1]; # id } } return; } # The POD index entries sub idx { my ($self,@idx) = @_; if(@idx) { push(@{$self->{-idx}}, @idx); return @idx; } else { return @{$self->{-idx}}; } } 1; ############################################################################# # Pod/Parser.pm -- package which defines a base class for parsing POD docs. # # Copyright (C) 1996-2000 by Bradford Appleton. All rights reserved. # This file is part of "PodParser". PodParser is free software; # you can redistribute it and/or modify it under the same terms # as Perl itself. ############################################################################# package Pod::Parser; use strict; ## These "variables" are used as local "glob aliases" for performance use vars qw($VERSION @ISA %myData %myOpts @input_stack); $VERSION = '1.63'; ## Current version of this package require 5.005; ## requires this Perl version or later ############################################################################# ############################################################################# #use diagnostics; use Pod::InputObjects; use Carp; use Exporter; BEGIN { if ($] < 5.006) { require Symbol; import Symbol; } } @ISA = qw(Exporter); ############################################################################# ##--------------------------------------------------------------------------- sub command { my ($self, $cmd, $text, $line_num, $pod_para) = @_; ## Just treat this like a textblock $self->textblock($pod_para->raw_text(), $line_num, $pod_para); } ##--------------------------------------------------------------------------- sub verbatim { my ($self, $text, $line_num, $pod_para) = @_; my $out_fh = $self->{_OUTPUT}; print $out_fh $text; } ##--------------------------------------------------------------------------- sub textblock { my ($self, $text, $line_num, $pod_para) = @_; my $out_fh = $self->{_OUTPUT}; print $out_fh $self->interpolate($text, $line_num); } ##--------------------------------------------------------------------------- sub interior_sequence { my ($self, $seq_cmd, $seq_arg, $pod_seq) = @_; ## Just return the raw text of the interior sequence return $pod_seq->raw_text(); } ############################################################################# ##--------------------------------------------------------------------------- sub new { ## Determine if we were called via an object-ref or a classname my ($this,%params) = @_; my $class = ref($this) || $this; ## Any remaining arguments are treated as initial values for the ## hash that is used to represent this object. my $self = { %params }; ## Bless ourselves into the desired class and perform any initialization bless $self, $class; $self->initialize(); return $self; } ##--------------------------------------------------------------------------- sub initialize { #my $self = shift; #return; } ##--------------------------------------------------------------------------- sub begin_pod { #my $self = shift; #return; } ##--------------------------------------------------------------------------- sub begin_input { #my $self = shift; #return; } ##--------------------------------------------------------------------------- sub end_input { #my $self = shift; #return; } ##--------------------------------------------------------------------------- sub end_pod { #my $self = shift; #return; } ##--------------------------------------------------------------------------- sub preprocess_line { my ($self, $text, $line_num) = @_; return $text; } ##--------------------------------------------------------------------------- sub preprocess_paragraph { my ($self, $text, $line_num) = @_; return $text; } ############################################################################# ##--------------------------------------------------------------------------- sub parse_text { my $self = shift; local $_ = ''; ## Get options and set any defaults my %opts = (ref $_[0]) ? %{ shift() } : (); my $expand_seq = $opts{'-expand_seq'} || undef; my $expand_text = $opts{'-expand_text'} || undef; my $expand_ptree = $opts{'-expand_ptree'} || undef; my $text = shift; my $line = shift; my $file = $self->input_file(); my $cmd = ""; ## Convert method calls into closures, for our convenience my $xseq_sub = $expand_seq; my $xtext_sub = $expand_text; my $xptree_sub = $expand_ptree; if (defined $expand_seq and $expand_seq eq 'interior_sequence') { ## If 'interior_sequence' is the method to use, we have to pass ## more than just the sequence object, we also need to pass the ## sequence name and text. $xseq_sub = sub { my ($sself, $iseq) = @_; my $args = join('', $iseq->parse_tree->children); return $sself->interior_sequence($iseq->name, $args, $iseq); }; } ref $xseq_sub or $xseq_sub = sub { shift()->$expand_seq(@_) }; ref $xtext_sub or $xtext_sub = sub { shift()->$expand_text(@_) }; ref $xptree_sub or $xptree_sub = sub { shift()->$expand_ptree(@_) }; ## Keep track of the "current" interior sequence, and maintain a stack ## of "in progress" sequences. ## ## NOTE that we push our own "accumulator" at the very beginning of the ## stack. It's really a parse-tree, not a sequence; but it implements ## the methods we need so we can use it to gather-up all the sequences ## and strings we parse. Thus, by the end of our parsing, it should be ## the only thing left on our stack and all we have to do is return it! ## my $seq = Pod::ParseTree->new(); my @seq_stack = ($seq); my ($ldelim, $rdelim) = ('', ''); ## Iterate over all sequence starts text (NOTE: split with ## capturing parens keeps the delimiters) $_ = $text; my @tokens = split /([A-Z]<(?:<+(?:\r?\n|[ \t]))?)/; while ( @tokens ) { $_ = shift @tokens; ## Look for the beginning of a sequence if ( /^([A-Z])(<(?:<+(?:\r?\n|[ \t]))?)$/ ) { ## Push a new sequence onto the stack of those "in-progress" my $ldelim_orig; ($cmd, $ldelim_orig) = ($1, $2); ($ldelim = $ldelim_orig) =~ s/\s+$//; ($rdelim = $ldelim) =~ tr//; $seq = Pod::InteriorSequence->new( -name => $cmd, -ldelim => $ldelim_orig, -rdelim => $rdelim, -file => $file, -line => $line ); (@seq_stack > 1) and $seq->nested($seq_stack[-1]); push @seq_stack, $seq; } ## Look for sequence ending elsif ( @seq_stack > 1 ) { ## Make sure we match the right kind of closing delimiter my ($seq_end, $post_seq) = ('', ''); if ( ($ldelim eq '<' and /\A(.*?)(>)/s) or /\A(.*?)(\s+$rdelim)/s ) { ## Found end-of-sequence, capture the interior and the ## closing the delimiter, and put the rest back on the ## token-list $post_seq = substr($_, length($1) + length($2)); ($_, $seq_end) = ($1, $2); (length $post_seq) and unshift @tokens, $post_seq; } if (length) { ## In the middle of a sequence, append this text to it, and ## don't forget to "expand" it if that's what the caller wanted $seq->append($expand_text ? &$xtext_sub($self,$_,$seq) : $_); $_ .= $seq_end; } if (length $seq_end) { ## End of current sequence, record terminating delimiter $seq->rdelim($seq_end); ## Pop it off the stack of "in progress" sequences pop @seq_stack; ## Append result to its parent in current parse tree $seq_stack[-1]->append($expand_seq ? &$xseq_sub($self,$seq) : $seq); ## Remember the current cmd-name and left-delimiter if(@seq_stack > 1) { $cmd = $seq_stack[-1]->name; $ldelim = $seq_stack[-1]->ldelim; $rdelim = $seq_stack[-1]->rdelim; } else { $cmd = $ldelim = $rdelim = ''; } } } elsif (length) { ## In the middle of a sequence, append this text to it, and ## don't forget to "expand" it if that's what the caller wanted $seq->append($expand_text ? &$xtext_sub($self,$_,$seq) : $_); } ## Keep track of line count $line += /\n/; ## Remember the "current" sequence $seq = $seq_stack[-1]; } ## Handle unterminated sequences my $errorsub = (@seq_stack > 1) ? $self->errorsub() : undef; while (@seq_stack > 1) { ($cmd, $file, $line) = ($seq->name, $seq->file_line); $ldelim = $seq->ldelim; ($rdelim = $ldelim) =~ tr//; $rdelim =~ s/^(\S+)(\s*)$/$2$1/; pop @seq_stack; my $errmsg = "*** ERROR: unterminated ${cmd}${ldelim}...${rdelim}". " at line $line in file $file\n"; (ref $errorsub) and &{$errorsub}($errmsg) or (defined $errorsub) and $self->$errorsub($errmsg) or carp($errmsg); $seq_stack[-1]->append($expand_seq ? &$xseq_sub($self,$seq) : $seq); $seq = $seq_stack[-1]; } ## Return the resulting parse-tree my $ptree = (pop @seq_stack)->parse_tree; return $expand_ptree ? &$xptree_sub($self, $ptree) : $ptree; } ##--------------------------------------------------------------------------- sub interpolate { my($self, $text, $line_num) = @_; my %parse_opts = ( -expand_seq => 'interior_sequence' ); my $ptree = $self->parse_text( \%parse_opts, $text, $line_num ); return join '', $ptree->children(); } ##--------------------------------------------------------------------------- sub parse_paragraph { my ($self, $text, $line_num) = @_; local *myData = $self; ## alias to avoid deref-ing overhead local *myOpts = ($myData{_PARSEOPTS} ||= {}); ## get parse-options local $_; ## See if we want to preprocess nonPOD paragraphs as well as POD ones. my $wantNonPods = $myOpts{'-want_nonPODs'}; ## Update cutting status $myData{_CUTTING} = 0 if $text =~ /^={1,2}\S/; ## Perform any desired preprocessing if we wanted it this early $wantNonPods and $text = $self->preprocess_paragraph($text, $line_num); ## Ignore up until next POD directive if we are cutting return if $myData{_CUTTING}; ## Now we know this is block of text in a POD section! ##----------------------------------------------------------------- ## This is a hook (hack ;-) for Pod::Select to do its thing without ## having to override methods, but also without Pod::Parser assuming ## $self is an instance of Pod::Select (if the _SELECTED_SECTIONS ## field exists then we assume there is an is_selected() method for ## us to invoke (calling $self->can('is_selected') could verify this ## but that is more overhead than I want to incur) ##----------------------------------------------------------------- ## Ignore this block if it isn't in one of the selected sections if (exists $myData{_SELECTED_SECTIONS}) { $self->is_selected($text) or return ($myData{_CUTTING} = 1); } ## If we haven't already, perform any desired preprocessing and ## then re-check the "cutting" state unless ($wantNonPods) { $text = $self->preprocess_paragraph($text, $line_num); return 1 unless ((defined $text) and (length $text)); return 1 if ($myData{_CUTTING}); } ## Look for one of the three types of paragraphs my ($pfx, $cmd, $arg, $sep) = ('', '', '', ''); my $pod_para = undef; if ($text =~ /^(={1,2})(?=\S)/) { ## Looks like a command paragraph. Capture the command prefix used ## ("=" or "=="), as well as the command-name, its paragraph text, ## and whatever sequence of characters was used to separate them $pfx = $1; $_ = substr($text, length $pfx); ($cmd, $sep, $text) = split /(\s+)/, $_, 2; $sep = '' unless defined $sep; $text = '' unless defined $text; ## If this is a "cut" directive then we don't need to do anything ## except return to "cutting" mode. if ($cmd eq 'cut') { $myData{_CUTTING} = 1; return unless $myOpts{'-process_cut_cmd'}; } } ## Save the attributes indicating how the command was specified. $pod_para = new Pod::Paragraph( -name => $cmd, -text => $text, -prefix => $pfx, -separator => $sep, -file => $myData{_INFILE}, -line => $line_num ); # ## Invoke appropriate callbacks # if (exists $myData{_CALLBACKS}) { # ## Look through the callback list, invoke callbacks, # ## then see if we need to do the default actions # ## (invoke_callbacks will return true if we do). # return 1 unless $self->invoke_callbacks($cmd, $text, $line_num, $pod_para); # } # If the last paragraph ended in whitespace, and we're not between verbatim blocks, carp if ($myData{_WHITESPACE} and $myOpts{'-warnings'} and not ($text =~ /^\s+/ and ($myData{_PREVIOUS}||"") eq "verbatim")) { my $errorsub = $self->errorsub(); my $line = $line_num - 1; my $errmsg = "*** WARNING: line containing nothing but whitespace". " in paragraph at line $line in file $myData{_INFILE}\n"; (ref $errorsub) and &{$errorsub}($errmsg) or (defined $errorsub) and $self->$errorsub($errmsg) or carp($errmsg); } if (length $cmd) { ## A command paragraph $self->command($cmd, $text, $line_num, $pod_para); $myData{_PREVIOUS} = $cmd; } elsif ($text =~ /^\s+/) { ## Indented text - must be a verbatim paragraph $self->verbatim($text, $line_num, $pod_para); $myData{_PREVIOUS} = "verbatim"; } else { ## Looks like an ordinary block of text $self->textblock($text, $line_num, $pod_para); $myData{_PREVIOUS} = "textblock"; } # Update the whitespace for the next time around #$myData{_WHITESPACE} = $text =~ /^[^\S\r\n]+\Z/m ? 1 : 0; $myData{_WHITESPACE} = $text =~ /^[^\S\r\n]+\r*\Z/m ? 1 : 0; return 1; } ##--------------------------------------------------------------------------- sub parse_from_filehandle { my $self = shift; my %opts = (ref $_[0] eq 'HASH') ? %{ shift() } : (); my ($in_fh, $out_fh) = @_; $in_fh = \*STDIN unless ($in_fh); local *myData = $self; ## alias to avoid deref-ing overhead local *myOpts = ($myData{_PARSEOPTS} ||= {}); ## get parse-options local $_; ## Put this stream at the top of the stack and do beginning-of-input ## processing. NOTE that $in_fh might be reset during this process. my $topstream = $self->_push_input_stream($in_fh, $out_fh); (exists $opts{-cutting}) and $self->cutting( $opts{-cutting} ); ## Initialize line/paragraph my ($textline, $paragraph) = ('', ''); my ($nlines, $plines) = (0, 0); ## Use <$fh> instead of $fh->getline where possible (for speed) $_ = ref $in_fh; my $tied_fh = (/^(?:GLOB|FileHandle|IO::\w+)$/ or tied $in_fh); ## Read paragraphs line-by-line while (defined ($textline = $tied_fh ? <$in_fh> : $in_fh->getline)) { $textline = $self->preprocess_line($textline, ++$nlines); next unless ((defined $textline) && (length $textline)); if ((! length $paragraph) && ($textline =~ /^==/)) { ## '==' denotes a one-line command paragraph $paragraph = $textline; $plines = 1; $textline = ''; } else { ## Append this line to the current paragraph $paragraph .= $textline; ++$plines; } ## See if this line is blank and ends the current paragraph. ## If it isn't, then keep iterating until it is. next unless (($textline =~ /^[^\S\r\n]*[\r\n]*$/) && (length $paragraph)); ## Now process the paragraph parse_paragraph($self, $paragraph, ($nlines - $plines) + 1); $paragraph = ''; $plines = 0; } ## Don't forget about the last paragraph in the file if (length $paragraph) { parse_paragraph($self, $paragraph, ($nlines - $plines) + 1) } ## Now pop the input stream off the top of the input stack. $self->_pop_input_stream(); } ##--------------------------------------------------------------------------- sub parse_from_file { my $self = shift; my %opts = (ref $_[0] eq 'HASH') ? %{ shift() } : (); my ($infile, $outfile) = @_; my ($in_fh, $out_fh); if ($] < 5.006) { ($in_fh, $out_fh) = (gensym(), gensym()); } my ($close_input, $close_output) = (0, 0); local *myData = $self; local *_; ## Is $infile a filename or a (possibly implied) filehandle if (defined $infile && ref $infile) { if (ref($infile) =~ /^(SCALAR|ARRAY|HASH|CODE|REF)$/) { croak "Input from $1 reference not supported!\n"; } ## Must be a filehandle-ref (or else assume its a ref to an object ## that supports the common IO read operations). $myData{_INFILE} = ${$infile}; $in_fh = $infile; } elsif (!defined($infile) || !length($infile) || ($infile eq '-') || ($infile =~ /^<&(?:STDIN|0)$/i)) { ## Not a filename, just a string implying STDIN $infile ||= '-'; $myData{_INFILE} = ''; $in_fh = \*STDIN; } else { ## We have a filename, open it for reading $myData{_INFILE} = $infile; open($in_fh, "< $infile") or croak "Can't open $infile for reading: $!\n"; $close_input = 1; } ## NOTE: we need to be *very* careful when "defaulting" the output ## file. We only want to use a default if this is the beginning of ## the entire document (but *not* if this is an included file). We ## determine this by seeing if the input stream stack has been set-up ## already ## Is $outfile a filename, a (possibly implied) filehandle, maybe a ref? if (ref $outfile) { ## we need to check for ref() first, as other checks involve reading if (ref($outfile) =~ /^(ARRAY|HASH|CODE)$/) { croak "Output to $1 reference not supported!\n"; } elsif (ref($outfile) eq 'SCALAR') { # # NOTE: IO::String isn't a part of the perl distribution, # # so probably we shouldn't support this case... # require IO::String; # $myData{_OUTFILE} = "$outfile"; # $out_fh = IO::String->new($outfile); croak "Output to SCALAR reference not supported!\n"; } else { ## Must be a filehandle-ref (or else assume its a ref to an ## object that supports the common IO write operations). $myData{_OUTFILE} = ${$outfile}; $out_fh = $outfile; } } elsif (!defined($outfile) || !length($outfile) || ($outfile eq '-') || ($outfile =~ /^>&?(?:STDOUT|1)$/i)) { if (defined $myData{_TOP_STREAM}) { $out_fh = $myData{_OUTPUT}; } else { ## Not a filename, just a string implying STDOUT $outfile ||= '-'; $myData{_OUTFILE} = ''; $out_fh = \*STDOUT; } } elsif ($outfile =~ /^>&(STDERR|2)$/i) { ## Not a filename, just a string implying STDERR $myData{_OUTFILE} = ''; $out_fh = \*STDERR; } else { ## We have a filename, open it for writing $myData{_OUTFILE} = $outfile; (-d $outfile) and croak "$outfile is a directory, not POD input!\n"; open($out_fh, "> $outfile") or croak "Can't open $outfile for writing: $!\n"; $close_output = 1; } ## Whew! That was a lot of work to set up reasonably/robust behavior ## in the case of a non-filename for reading and writing. Now we just ## have to parse the input and close the handles when we're finished. $self->parse_from_filehandle(\%opts, $in_fh, $out_fh); $close_input and close($in_fh) || croak "Can't close $infile after reading: $!\n"; $close_output and close($out_fh) || croak "Can't close $outfile after writing: $!\n"; } ############################################################################# ##--------------------------------------------------------------------------- sub errorsub { return (@_ > 1) ? ($_[0]->{_ERRORSUB} = $_[1]) : $_[0]->{_ERRORSUB}; } ##--------------------------------------------------------------------------- sub cutting { return (@_ > 1) ? ($_[0]->{_CUTTING} = $_[1]) : $_[0]->{_CUTTING}; } ##--------------------------------------------------------------------------- ##--------------------------------------------------------------------------- sub parseopts { local *myData = shift; local *myOpts = ($myData{_PARSEOPTS} ||= {}); return %myOpts if (@_ == 0); if (@_ == 1) { local $_ = shift; return ref($_) ? $myData{_PARSEOPTS} = $_ : $myOpts{$_}; } my @newOpts = (%myOpts, @_); $myData{_PARSEOPTS} = { @newOpts }; } ##--------------------------------------------------------------------------- sub output_file { return $_[0]->{_OUTFILE}; } ##--------------------------------------------------------------------------- sub output_handle { return $_[0]->{_OUTPUT}; } ##--------------------------------------------------------------------------- sub input_file { return $_[0]->{_INFILE}; } ##--------------------------------------------------------------------------- sub input_handle { return $_[0]->{_INPUT}; } ##--------------------------------------------------------------------------- sub input_streams { return $_[0]->{_INPUT_STREAMS}; } ##--------------------------------------------------------------------------- sub top_stream { return $_[0]->{_TOP_STREAM} || undef; } ############################################################################# ##--------------------------------------------------------------------------- sub _push_input_stream { my ($self, $in_fh, $out_fh) = @_; local *myData = $self; ## Initialize stuff for the entire document if this is *not* ## an included file. ## ## NOTE: we need to be *very* careful when "defaulting" the output ## filehandle. We only want to use a default value if this is the ## beginning of the entire document (but *not* if this is an included ## file). unless (defined $myData{_TOP_STREAM}) { $out_fh = \*STDOUT unless (defined $out_fh); $myData{_CUTTING} = 1; ## current "cutting" state $myData{_INPUT_STREAMS} = []; ## stack of all input streams } ## Initialize input indicators $myData{_OUTFILE} = '(unknown)' unless (defined $myData{_OUTFILE}); $myData{_OUTPUT} = $out_fh if (defined $out_fh); $in_fh = \*STDIN unless (defined $in_fh); $myData{_INFILE} = '(unknown)' unless (defined $myData{_INFILE}); $myData{_INPUT} = $in_fh; my $input_top = $myData{_TOP_STREAM} = new Pod::InputSource( -name => $myData{_INFILE}, -handle => $in_fh, -was_cutting => $myData{_CUTTING} ); local *input_stack = $myData{_INPUT_STREAMS}; push(@input_stack, $input_top); ## Perform beginning-of-document and/or beginning-of-input processing $self->begin_pod() if (@input_stack == 1); $self->begin_input(); return $input_top; } ##--------------------------------------------------------------------------- sub _pop_input_stream { my ($self) = @_; local *myData = $self; local *input_stack = $myData{_INPUT_STREAMS}; ## Perform end-of-input and/or end-of-document processing $self->end_input() if (@input_stack > 0); $self->end_pod() if (@input_stack == 1); ## Restore cutting state to whatever it was before we started ## parsing this file. my $old_top = pop(@input_stack); $myData{_CUTTING} = $old_top->was_cutting(); ## Don't forget to reset the input indicators my $input_top = undef; if (@input_stack > 0) { $input_top = $myData{_TOP_STREAM} = $input_stack[-1]; $myData{_INFILE} = $input_top->name(); $myData{_INPUT} = $input_top->handle(); } else { delete $myData{_TOP_STREAM}; delete $myData{_INPUT_STREAMS}; } return $input_top; } ############################################################################# 1; # vim: ts=4 sw=4 et package Pod::Html; use strict; require Exporter; our $VERSION = 1.24; our @ISA = qw(Exporter); our @EXPORT = qw(pod2html htmlify); our @EXPORT_OK = qw(anchorify relativize_url); use Carp; use Config; use Cwd; use File::Basename; use File::Spec; use File::Spec::Unix; use Getopt::Long; use Pod::Simple::Search; use Pod::Simple::SimpleTree (); use locale; # make \w work right in non-ASCII lands # This sub duplicates the guts of Pod::Simple::FromTree. We could have # used that module, except that it would have been a non-core dependency. sub feed_tree_to_parser { my($parser, $tree) = @_; if(ref($tree) eq "") { $parser->_handle_text($tree); } elsif(!($tree->[0] eq "X" && $parser->nix_X_codes)) { $parser->_handle_element_start($tree->[0], $tree->[1]); feed_tree_to_parser($parser, $_) foreach @{$tree}[2..$#$tree]; $parser->_handle_element_end($tree->[0]); } } my $Cachedir; my $Dircache; my($Htmlroot, $Htmldir, $Htmlfile, $Htmlfileurl); my($Podfile, @Podpath, $Podroot); my $Poderrors; my $Css; my $Recurse; my $Quiet; my $Verbose; my $Doindex; my $Backlink; my($Title, $Header); my %Pages = (); # associative array used to find the location # of pages referenced by L<> links. my $Curdir = File::Spec->curdir; init_globals(); sub init_globals { $Cachedir = "."; # The directory to which directory caches # will be written. $Dircache = "pod2htmd.tmp"; $Htmlroot = "/"; # http-server base directory from which all # relative paths in $podpath stem. $Htmldir = ""; # The directory to which the html pages # will (eventually) be written. $Htmlfile = ""; # write to stdout by default $Htmlfileurl = ""; # The url that other files would use to # refer to this file. This is only used # to make relative urls that point to # other files. $Poderrors = 1; $Podfile = ""; # read from stdin by default @Podpath = (); # list of directories containing library pods. $Podroot = $Curdir; # filesystem base directory from which all # relative paths in $podpath stem. $Css = ''; # Cascading style sheet $Recurse = 1; # recurse on subdirectories in $podpath. $Quiet = 0; # not quiet by default $Verbose = 0; # not verbose by default $Doindex = 1; # non-zero if we should generate an index $Backlink = 0; # no backlinks added by default $Header = 0; # produce block header/footer $Title = undef; # title to give the pod(s) } sub pod2html { local(@ARGV) = @_; local $_; init_globals(); parse_command_line(); # prevent '//' in urls $Htmlroot = "" if $Htmlroot eq "/"; $Htmldir =~ s#/\z##; if ( $Htmlroot eq '' && defined( $Htmldir ) && $Htmldir ne '' && substr( $Htmlfile, 0, length( $Htmldir ) ) eq $Htmldir ) { # Set the 'base' url for this file, so that we can use it # as the location from which to calculate relative links # to other files. If this is '', then absolute links will # be used throughout. #$Htmlfileurl = "$Htmldir/" . substr( $Htmlfile, length( $Htmldir ) + 1); # Is the above not just "$Htmlfileurl = $Htmlfile"? $Htmlfileurl = Pod::Html::_unixify($Htmlfile); } # load or generate/cache %Pages unless (get_cache($Dircache, \@Podpath, $Podroot, $Recurse)) { # generate %Pages my $pwd = getcwd(); chdir($Podroot) || die "$0: error changing to directory $Podroot: $!\n"; # find all pod modules/pages in podpath, store in %Pages # - callback used to remove Podroot and extension from each file # - laborious to allow '.' in dirnames (e.g., /usr/share/perl/5.14.1) Pod::Simple::Search->new->inc(0)->verbose($Verbose)->laborious(1) ->callback(\&_save_page)->recurse($Recurse)->survey(@Podpath); chdir($pwd) || die "$0: error changing to directory $pwd: $!\n"; # cache the directory list for later use warn "caching directories for later use\n" if $Verbose; open my $cache, '>', $Dircache or die "$0: error open $Dircache for writing: $!\n"; print $cache join(":", @Podpath) . "\n$Podroot\n"; my $_updirs_only = ($Podroot =~ /\.\./) && !($Podroot =~ /[^\.\\\/]/); foreach my $key (keys %Pages) { if($_updirs_only) { my $_dirlevel = $Podroot; while($_dirlevel =~ /\.\./) { $_dirlevel =~ s/\.\.//; # Assume $Pages{$key} has '/' separators (html dir separators). $Pages{$key} =~ s/^[\w\s\-\.]+\///; } } print $cache "$key $Pages{$key}\n"; } close $cache or die "error closing $Dircache: $!"; } my $input; unless (@ARGV && $ARGV[0]) { if ($Podfile and $Podfile ne '-') { $input = $Podfile; } else { $input = '-'; # XXX: make a test case for this } } else { $Podfile = $ARGV[0]; $input = *ARGV; } # set options for input parser my $parser = Pod::Simple::SimpleTree->new; $parser->codes_in_verbatim(0); $parser->accept_targets(qw(html HTML)); $parser->no_errata_section(!$Poderrors); # note the inverse warn "Converting input file $Podfile\n" if $Verbose; my $podtree = $parser->parse_file($input)->root; unless(defined $Title) { if($podtree->[0] eq "Document" && ref($podtree->[2]) eq "ARRAY" && $podtree->[2]->[0] eq "head1" && @{$podtree->[2]} == 3 && ref($podtree->[2]->[2]) eq "" && $podtree->[2]->[2] eq "NAME" && ref($podtree->[3]) eq "ARRAY" && $podtree->[3]->[0] eq "Para" && @{$podtree->[3]} >= 3 && !(grep { ref($_) ne "" } @{$podtree->[3]}[2..$#{$podtree->[3]}]) && (@$podtree == 4 || (ref($podtree->[4]) eq "ARRAY" && $podtree->[4]->[0] eq "head1"))) { $Title = join("", @{$podtree->[3]}[2..$#{$podtree->[3]}]); } } $Title //= ""; $Title = html_escape($Title); # set options for the HTML generator $parser = Pod::Simple::XHTML::LocalPodLinks->new(); $parser->codes_in_verbatim(0); $parser->anchor_items(1); # the old Pod::Html always did $parser->backlink($Backlink); # linkify =head1 directives $parser->force_title($Title); $parser->htmldir($Htmldir); $parser->htmlfileurl($Htmlfileurl); $parser->htmlroot($Htmlroot); $parser->index($Doindex); $parser->output_string(\my $output); # written to file later $parser->pages(\%Pages); $parser->quiet($Quiet); $parser->verbose($Verbose); # We need to add this ourselves because we use our own header, not # ::XHTML's header. We need to set $parser->backlink to linkify # the =head1 directives my $bodyid = $Backlink ? ' id="_podtop_"' : ''; my $csslink = ''; my $tdstyle = ' style="background-color: #cccccc; color: #000"'; if ($Css) { $csslink = qq(\n); $csslink =~ s,\\,/,g; $csslink =~ s,(/.):,$1|,; $tdstyle= ''; } # header/footer block my $block = $Header ? <  $Title END_OF_BLOCK # create own header/footer because of --header $parser->html_header(<<"HTMLHEAD"); $Title$csslink $block HTMLHEAD $parser->html_footer(<<"HTMLFOOT"); $block HTMLFOOT feed_tree_to_parser($parser, $podtree); # Write output to file $Htmlfile = "-" unless $Htmlfile; # stdout my $fhout; if($Htmlfile and $Htmlfile ne '-') { open $fhout, ">", $Htmlfile or die "$0: cannot open $Htmlfile file for output: $!\n"; } else { open $fhout, ">-"; } binmode $fhout, ":utf8"; print $fhout $output; close $fhout or die "Failed to close $Htmlfile: $!"; chmod 0644, $Htmlfile unless $Htmlfile eq '-'; } ############################################################################## sub usage { my $podfile = shift; warn "$0: $podfile: @_\n" if @_; die < --htmlroot= --infile= --outfile= --podpath=:...: --podroot= --cachedir= --flush --recurse --norecurse --quiet --noquiet --verbose --noverbose --index --noindex --backlink --nobacklink --header --noheader --poderrors --nopoderrors --css= --title= --[no]backlink - turn =head1 directives into links pointing to the top of the page (off by default). --cachedir - directory for the directory cache files. --css - stylesheet URL --flush - flushes the directory cache. --[no]header - produce block header/footer (default is no headers). --help - prints this message. --htmldir - directory for resulting HTML files. --htmlroot - http-server base directory from which all relative paths in podpath stem (default is /). --[no]index - generate an index at the top of the resulting html (default behaviour). --infile - filename for the pod to convert (input taken from stdin by default). --outfile - filename for the resulting html file (output sent to stdout by default). --[no]poderrors - include a POD ERRORS section in the output if there were any POD errors in the input (default behavior). --podpath - colon-separated list of directories containing library pods (empty by default). --podroot - filesystem base directory from which all relative paths in podpath stem (default is .). --[no]quiet - suppress some benign warning messages (default is off). --[no]recurse - recurse on those subdirectories listed in podpath (default behaviour). --title - title that will appear in resulting html file. --[no]verbose - self-explanatory (off by default). END_OF_USAGE } sub parse_command_line { my ($opt_backlink,$opt_cachedir,$opt_css,$opt_flush,$opt_header, $opt_help,$opt_htmldir,$opt_htmlroot,$opt_index,$opt_infile, $opt_outfile,$opt_poderrors,$opt_podpath,$opt_podroot, $opt_quiet,$opt_recurse,$opt_title,$opt_verbose); unshift @ARGV, split ' ', $Config{pod2html} if $Config{pod2html}; my $result = GetOptions( 'backlink!' => \$opt_backlink, 'cachedir=s' => \$opt_cachedir, 'css=s' => \$opt_css, 'flush' => \$opt_flush, 'help' => \$opt_help, 'header!' => \$opt_header, 'htmldir=s' => \$opt_htmldir, 'htmlroot=s' => \$opt_htmlroot, 'index!' => \$opt_index, 'infile=s' => \$opt_infile, 'outfile=s' => \$opt_outfile, 'poderrors!' => \$opt_poderrors, 'podpath=s' => \$opt_podpath, 'podroot=s' => \$opt_podroot, 'quiet!' => \$opt_quiet, 'recurse!' => \$opt_recurse, 'title=s' => \$opt_title, 'verbose!' => \$opt_verbose, ); usage("-", "invalid parameters") if not $result; usage("-") if defined $opt_help; # see if the user asked for help $opt_help = ""; # just to make -w shut-up. @Podpath = split(":", $opt_podpath) if defined $opt_podpath; $Backlink = $opt_backlink if defined $opt_backlink; $Cachedir = _unixify($opt_cachedir) if defined $opt_cachedir; $Css = $opt_css if defined $opt_css; $Header = $opt_header if defined $opt_header; $Htmldir = _unixify($opt_htmldir) if defined $opt_htmldir; $Htmlroot = _unixify($opt_htmlroot) if defined $opt_htmlroot; $Doindex = $opt_index if defined $opt_index; $Podfile = _unixify($opt_infile) if defined $opt_infile; $Htmlfile = _unixify($opt_outfile) if defined $opt_outfile; $Poderrors = $opt_poderrors if defined $opt_poderrors; $Podroot = _unixify($opt_podroot) if defined $opt_podroot; $Quiet = $opt_quiet if defined $opt_quiet; $Recurse = $opt_recurse if defined $opt_recurse; $Title = $opt_title if defined $opt_title; $Verbose = $opt_verbose if defined $opt_verbose; warn "Flushing directory caches\n" if $opt_verbose && defined $opt_flush; $Dircache = "$Cachedir/pod2htmd.tmp"; if (defined $opt_flush) { 1 while unlink($Dircache); } } my $Saved_Cache_Key; sub get_cache { my($dircache, $podpath, $podroot, $recurse) = @_; my @cache_key_args = @_; # A first-level cache: # Don't bother reading the cache files if they still apply # and haven't changed since we last read them. my $this_cache_key = cache_key(@cache_key_args); return 1 if $Saved_Cache_Key and $this_cache_key eq $Saved_Cache_Key; $Saved_Cache_Key = $this_cache_key; # load the cache of %Pages if possible. $tests will be # non-zero if successful. my $tests = 0; if (-f $dircache) { warn "scanning for directory cache\n" if $Verbose; $tests = load_cache($dircache, $podpath, $podroot); } return $tests; } sub cache_key { my($dircache, $podpath, $podroot, $recurse) = @_; return join('!',$dircache,$recurse,@$podpath,$podroot,stat($dircache)); } # # load_cache - tries to find if the cache stored in $dircache is a valid # cache of %Pages. if so, it loads them and returns a non-zero value. # sub load_cache { my($dircache, $podpath, $podroot) = @_; my $tests = 0; local $_; warn "scanning for directory cache\n" if $Verbose; open(my $cachefh, '<', $dircache) || die "$0: error opening $dircache for reading: $!\n"; $/ = "\n"; # is it the same podpath? $_ = <$cachefh>; chomp($_); $tests++ if (join(":", @$podpath) eq $_); # is it the same podroot? $_ = <$cachefh>; chomp($_); $tests++ if ($podroot eq $_); # load the cache if its good if ($tests != 2) { close($cachefh); return 0; } warn "loading directory cache\n" if $Verbose; while (<$cachefh>) { /(.*?) (.*)$/; $Pages{$1} = $2; } close($cachefh); return 1; } # # html_escape: make text safe for HTML # sub html_escape { my $rest = $_[0]; $rest =~ s/&/&/g; $rest =~ s//>/g; $rest =~ s/"/"/g; $rest =~ s/([[:^print:]])/sprintf("&#x%x;", ord($1))/aeg; return $rest; } # # htmlify - converts a pod section specification to a suitable section # specification for HTML. We adopt the mechanism used by the formatter # that we use. # sub htmlify { my( $heading) = @_; return Pod::Simple::XHTML->can("idify")->(undef, $heading, 1); } # # similar to htmlify, but turns non-alphanumerics into underscores # sub anchorify { my ($anchor) = @_; $anchor = htmlify($anchor); $anchor =~ s/\W/_/g; return $anchor; } # # store POD files in %Pages # sub _save_page { my ($modspec, $modname) = @_; # Remove Podroot from path $modspec = $Podroot eq File::Spec->curdir ? File::Spec->abs2rel($modspec) : File::Spec->abs2rel($modspec, File::Spec->canonpath($Podroot)); # Convert path to unix style path $modspec = Pod::Html::_unixify($modspec); my ($file, $dir) = fileparse($modspec, qr/\.[^.]*/); # strip .ext $Pages{$modname} = $dir.$file; } sub _unixify { my $full_path = shift; return '' unless $full_path; return $full_path if $full_path eq '/'; my ($vol, $dirs, $file) = File::Spec->splitpath($full_path); my @dirs = $dirs eq File::Spec->curdir() ? (File::Spec::Unix->curdir()) : File::Spec->splitdir($dirs); if (defined($vol) && $vol) { $vol =~ s/:$// if $^O eq 'VMS'; $vol = uc $vol if $^O eq 'MSWin32'; if( $dirs[0] ) { unshift @dirs, $vol; } else { $dirs[0] = $vol; } } unshift @dirs, '' if File::Spec->file_name_is_absolute($full_path); return $file unless scalar(@dirs); $full_path = File::Spec::Unix->catfile(File::Spec::Unix->catdir(@dirs), $file); $full_path =~ s|^\/|| if $^O eq 'MSWin32'; # C:/foo works, /C:/foo doesn't $full_path =~ s/\^\././g if $^O eq 'VMS'; # unescape dots return $full_path; } package Pod::Simple::XHTML::LocalPodLinks; use strict; use warnings; use parent 'Pod::Simple::XHTML'; use File::Spec; use File::Spec::Unix; __PACKAGE__->_accessorize( 'htmldir', 'htmlfileurl', 'htmlroot', 'pages', # Page name => relative/path/to/page from root POD dir 'quiet', 'verbose', ); sub resolve_pod_page_link { my ($self, $to, $section) = @_; return undef unless defined $to || defined $section; if (defined $section) { $section = '#' . $self->idify($section, 1); return $section unless defined $to; } else { $section = ''; } my $path; # path to $to according to %Pages unless (exists $self->pages->{$to}) { # Try to find a POD that ends with $to and use that. # e.g., given L, if there is no $Podpath/XHTML in %Pages, # look for $Podpath/*/XHTML in %Pages, with * being any path, # as a substitute (e.g., $Podpath/Pod/Simple/XHTML) my @matches; foreach my $modname (keys %{$self->pages}) { push @matches, $modname if $modname =~ /::\Q$to\E\z/; } if ($#matches == -1) { warn "Cannot find \"$to\" in podpath: " . "cannot find suitable replacement path, cannot resolve link\n" unless $self->quiet; return ''; } elsif ($#matches == 0) { warn "Cannot find \"$to\" in podpath: " . "using $matches[0] as replacement path to $to\n" unless $self->quiet; $path = $self->pages->{$matches[0]}; } else { warn "Cannot find \"$to\" in podpath: " . "more than one possible replacement path to $to, " . "using $matches[-1]\n" unless $self->quiet; # Use [-1] so newer (higher numbered) perl PODs are used $path = $self->pages->{$matches[-1]}; } } else { $path = $self->pages->{$to}; } my $url = File::Spec::Unix->catfile(Pod::Html::_unixify($self->htmlroot), $path); if ($self->htmlfileurl ne '') { # then $self->htmlroot eq '' (by definition of htmlfileurl) so # $self->htmldir needs to be prepended to link to get the absolute path # that will be relativized $url = Pod::Html::relativize_url( File::Spec::Unix->catdir(Pod::Html::_unixify($self->htmldir), $url), $self->htmlfileurl # already unixified ); } return $url . ".html$section"; } package Pod::Html; # # relativize_url - convert an absolute URL to one relative to a base URL. # Assumes both end in a filename. # sub relativize_url { my ($dest, $source) = @_; # Remove each file from its path my ($dest_volume, $dest_directory, $dest_file) = File::Spec::Unix->splitpath( $dest ); $dest = File::Spec::Unix->catpath( $dest_volume, $dest_directory, '' ); my ($source_volume, $source_directory, $source_file) = File::Spec::Unix->splitpath( $source ); $source = File::Spec::Unix->catpath( $source_volume, $source_directory, '' ); my $rel_path = ''; if ($dest ne '') { $rel_path = File::Spec::Unix->abs2rel( $dest, $source ); } if ($rel_path ne '' && substr( $rel_path, -1 ) ne '/') { $rel_path .= "/$dest_file"; } else { $rel_path .= "$dest_file"; } return $rel_path; } 1; ############################################################################# # Pod/Select.pm -- function to select portions of POD docs # # Copyright (C) 1996-2000 by Bradford Appleton. All rights reserved. # This file is part of "PodParser". PodParser is free software; # you can redistribute it and/or modify it under the same terms # as Perl itself. ############################################################################# package Pod::Select; use strict; use vars qw($VERSION @ISA @EXPORT $MAX_HEADING_LEVEL %myData @section_headings @selected_sections); $VERSION = '1.63'; ## Current version of this package require 5.005; ## requires this Perl version or later ############################################################################# ############################################################################# #use diagnostics; use Carp; use Pod::Parser 1.04; @ISA = qw(Pod::Parser); @EXPORT = qw(&podselect); ## Maximum number of heading levels supported for '=headN' directives *MAX_HEADING_LEVEL = \3; ############################################################################# ##--------------------------------------------------------------------------- ## =begin _PRIVATE_ ## ## =head1 B<_init_headings()> ## ## Initialize the current set of active section headings. ## ## =cut ## ## =end _PRIVATE_ sub _init_headings { my $self = shift; local *myData = $self; ## Initialize current section heading titles if necessary unless (defined $myData{_SECTION_HEADINGS}) { local *section_headings = $myData{_SECTION_HEADINGS} = []; for (my $i = 0; $i < $MAX_HEADING_LEVEL; ++$i) { $section_headings[$i] = ''; } } } ##--------------------------------------------------------------------------- sub curr_headings { my $self = shift; $self->_init_headings() unless (defined $self->{_SECTION_HEADINGS}); my @headings = @{ $self->{_SECTION_HEADINGS} }; return (@_ > 0 and $_[0] =~ /^\d+$/) ? $headings[$_[0] - 1] : @headings; } ##--------------------------------------------------------------------------- sub select { my ($self, @sections) = @_; local *myData = $self; local $_; ### NEED TO DISCERN A SECTION-SPEC FROM A RANGE-SPEC (look for m{^/.+/$}?) ##--------------------------------------------------------------------- ## The following is a blatant hack for backward compatibility, and for ## implementing add_selection(). If the *first* *argument* is the ## string "+", then the remaining section specifications are *added* ## to the current set of selections; otherwise the given section ## specifications will *replace* the current set of selections. ## ## This should probably be fixed someday, but for the present time, ## it seems incredibly unlikely that "+" would ever correspond to ## a legitimate section heading ##--------------------------------------------------------------------- my $add = ($sections[0] eq '+') ? shift(@sections) : ''; ## Reset the set of sections to use unless (@sections) { delete $myData{_SELECTED_SECTIONS} unless ($add); return; } $myData{_SELECTED_SECTIONS} = [] unless ($add && exists $myData{_SELECTED_SECTIONS}); local *selected_sections = $myData{_SELECTED_SECTIONS}; ## Compile each spec for my $spec (@sections) { if ( defined($_ = _compile_section_spec($spec)) ) { ## Store them in our sections array push(@selected_sections, $_); } else { carp qq{Ignoring section spec "$spec"!\n}; } } } ##--------------------------------------------------------------------------- sub add_selection { my $self = shift; return $self->select('+', @_); } ##--------------------------------------------------------------------------- sub clear_selections { my $self = shift; return $self->select(); } ##--------------------------------------------------------------------------- sub match_section { my $self = shift; my (@headings) = @_; local *myData = $self; ## Return true if no restrictions were explicitly specified my $selections = (exists $myData{_SELECTED_SECTIONS}) ? $myData{_SELECTED_SECTIONS} : undef; return 1 unless ((defined $selections) && @{$selections}); ## Default any unspecified sections to the current one my @current_headings = $self->curr_headings(); for (my $i = 0; $i < $MAX_HEADING_LEVEL; ++$i) { (defined $headings[$i]) or $headings[$i] = $current_headings[$i]; } ## Look for a match against the specified section expressions for my $section_spec ( @{$selections} ) { ##------------------------------------------------------ ## Each portion of this spec must match in order for ## the spec to be matched. So we will start with a ## match-value of 'true' and logically 'and' it with ## the results of matching a given element of the spec. ##------------------------------------------------------ my $match = 1; for (my $i = 0; $i < $MAX_HEADING_LEVEL; ++$i) { my $regex = $section_spec->[$i]; my $negated = ($regex =~ s/^\!//); $match &= ($negated ? ($headings[$i] !~ /${regex}/) : ($headings[$i] =~ /${regex}/)); last unless ($match); } return 1 if ($match); } return 0; ## no match } ##--------------------------------------------------------------------------- sub is_selected { my ($self, $paragraph) = @_; local $_; local *myData = $self; $self->_init_headings() unless (defined $myData{_SECTION_HEADINGS}); ## Keep track of current sections levels and headings $_ = $paragraph; if (/^=((?:sub)*)(?:head(?:ing)?|sec(?:tion)?)(\d*)\s+(.*?)\s*$/) { ## This is a section heading command my ($level, $heading) = ($2, $3); $level = 1 + (length($1) / 3) if ((! length $level) || (length $1)); ## Reset the current section heading at this level $myData{_SECTION_HEADINGS}->[$level - 1] = $heading; ## Reset subsection headings of this one to empty for (my $i = $level; $i < $MAX_HEADING_LEVEL; ++$i) { $myData{_SECTION_HEADINGS}->[$i] = ''; } } return $self->match_section(); } ############################################################################# ##--------------------------------------------------------------------------- sub podselect { my(@argv) = @_; my %defaults = (); my $pod_parser = new Pod::Select(%defaults); my $num_inputs = 0; my $output = '>&STDOUT'; my %opts; local $_; for (@argv) { my $ref = ref($_); if ($ref && $ref eq 'HASH') { %opts = (%defaults, %{$_}); ##------------------------------------------------------------- ## Need this for backward compatibility since we formerly used ## options that were all uppercase words rather than ones that ## looked like Unix command-line options. ## to be uppercase keywords) ##------------------------------------------------------------- %opts = map { my ($key, $val) = (lc $_, $opts{$_}); $key =~ s/^(?=\w)/-/; $key =~ /^-se[cl]/ and $key = '-sections'; #! $key eq '-range' and $key .= 's'; ($key => $val); } (keys %opts); ## Process the options (exists $opts{'-output'}) and $output = $opts{'-output'}; ## Select the desired sections $pod_parser->select(@{ $opts{'-sections'} }) if ( (defined $opts{'-sections'}) && ((ref $opts{'-sections'}) eq 'ARRAY') ); #! ## Select the desired paragraph ranges #! $pod_parser->select(@{ $opts{'-ranges'} }) #! if ( (defined $opts{'-ranges'}) #! && ((ref $opts{'-ranges'}) eq 'ARRAY') ); } elsif(!$ref || $ref eq 'GLOB') { $pod_parser->parse_from_file($_, $output); ++$num_inputs; } else { croak "Input from $ref reference not supported!\n"; } } $pod_parser->parse_from_file('-') unless ($num_inputs > 0); } ############################################################################# ##--------------------------------------------------------------------------- sub _compile_section_spec { my ($section_spec) = @_; my (@regexs, $negated); ## Compile the spec into a list of regexs local $_ = $section_spec; s{\\\\}{\001}g; ## handle escaped backward slashes s{\\/}{\002}g; ## handle escaped forward slashes ## Parse the regexs for the heading titles @regexs = split(/\//, $_, $MAX_HEADING_LEVEL); ## Set default regex for omitted levels for (my $i = 0; $i < $MAX_HEADING_LEVEL; ++$i) { $regexs[$i] = '.*' unless ((defined $regexs[$i]) && (length $regexs[$i])); } ## Modify the regexs as needed and validate their syntax my $bad_regexs = 0; for (@regexs) { $_ .= '.+' if ($_ eq '!'); s{\001}{\\\\}g; ## restore escaped backward slashes s{\002}{\\/}g; ## restore escaped forward slashes $negated = s/^\!//; ## check for negation eval "m{$_}"; ## check regex syntax if ($@) { ++$bad_regexs; carp qq{Bad regular expression /$_/ in "$section_spec": $@\n}; } else { ## Add the forward and rear anchors (and put the negator back) $_ = '^' . $_ unless (/^\^/); $_ = $_ . '$' unless (/\$$/); $_ = '!' . $_ if ($negated); } } return (! $bad_regexs) ? [ @regexs ] : undef; } ##--------------------------------------------------------------------------- ##--------------------------------------------------------------------------- ############################################################################# 1; # vim: ts=4 sw=4 et ############################################################################# # Pod/InputObjects.pm -- package which defines objects for input streams # and paragraphs and commands when parsing POD docs. # # Copyright (C) 1996-2000 by Bradford Appleton. All rights reserved. # This file is part of "PodParser". PodParser is free software; # you can redistribute it and/or modify it under the same terms # as Perl itself. ############################################################################# package Pod::InputObjects; use strict; use vars qw($VERSION); $VERSION = '1.63'; ## Current version of this package require 5.005; ## requires this Perl version or later ############################################################################# ############################################################################# package Pod::InputSource; ##--------------------------------------------------------------------------- ##--------------------------------------------------------------------------- sub new { ## Determine if we were called via an object-ref or a classname my $this = shift; my $class = ref($this) || $this; ## Any remaining arguments are treated as initial values for the ## hash that is used to represent this object. Note that we default ## certain values by specifying them *before* the arguments passed. ## If they are in the argument list, they will override the defaults. my $self = { -name => '(unknown)', -handle => undef, -was_cutting => 0, @_ }; ## Bless ourselves into the desired class and perform any initialization bless $self, $class; return $self; } ##--------------------------------------------------------------------------- sub name { (@_ > 1) and $_[0]->{'-name'} = $_[1]; return $_[0]->{'-name'}; } ## allow 'filename' as an alias for 'name' *filename = \&name; ##--------------------------------------------------------------------------- sub handle { return $_[0]->{'-handle'}; } ##--------------------------------------------------------------------------- sub was_cutting { (@_ > 1) and $_[0]->{-was_cutting} = $_[1]; return $_[0]->{-was_cutting}; } ##--------------------------------------------------------------------------- ############################################################################# package Pod::Paragraph; ##--------------------------------------------------------------------------- ##--------------------------------------------------------------------------- sub new { ## Determine if we were called via an object-ref or a classname my $this = shift; my $class = ref($this) || $this; ## Any remaining arguments are treated as initial values for the ## hash that is used to represent this object. Note that we default ## certain values by specifying them *before* the arguments passed. ## If they are in the argument list, they will override the defaults. my $self = { -name => undef, -text => (@_ == 1) ? shift : undef, -file => '', -line => 0, -prefix => '=', -separator => ' ', -ptree => [], @_ }; ## Bless ourselves into the desired class and perform any initialization bless $self, $class; return $self; } ##--------------------------------------------------------------------------- sub cmd_name { (@_ > 1) and $_[0]->{'-name'} = $_[1]; return $_[0]->{'-name'}; } ## let name() be an alias for cmd_name() *name = \&cmd_name; ##--------------------------------------------------------------------------- sub text { (@_ > 1) and $_[0]->{'-text'} = $_[1]; return $_[0]->{'-text'}; } ##--------------------------------------------------------------------------- sub raw_text { return $_[0]->{'-text'} unless (defined $_[0]->{'-name'}); return $_[0]->{'-prefix'} . $_[0]->{'-name'} . $_[0]->{'-separator'} . $_[0]->{'-text'}; } ##--------------------------------------------------------------------------- sub cmd_prefix { return $_[0]->{'-prefix'}; } ##--------------------------------------------------------------------------- sub cmd_separator { return $_[0]->{'-separator'}; } ##--------------------------------------------------------------------------- sub parse_tree { (@_ > 1) and $_[0]->{'-ptree'} = $_[1]; return $_[0]->{'-ptree'}; } ## let ptree() be an alias for parse_tree() *ptree = \&parse_tree; ##--------------------------------------------------------------------------- sub file_line { my @loc = ($_[0]->{'-file'} || '', $_[0]->{'-line'} || 0); return (wantarray) ? @loc : join(':', @loc); } ##--------------------------------------------------------------------------- ############################################################################# package Pod::InteriorSequence; ##--------------------------------------------------------------------------- ##--------------------------------------------------------------------------- sub new { ## Determine if we were called via an object-ref or a classname my $this = shift; my $class = ref($this) || $this; ## See if first argument has no keyword if (((@_ <= 2) or (@_ % 2)) and $_[0] !~ /^-\w/) { ## Yup - need an implicit '-name' before first parameter unshift @_, '-name'; } ## See if odd number of args if ((@_ % 2) != 0) { ## Yup - need an implicit '-ptree' before the last parameter splice @_, $#_, 0, '-ptree'; } ## Any remaining arguments are treated as initial values for the ## hash that is used to represent this object. Note that we default ## certain values by specifying them *before* the arguments passed. ## If they are in the argument list, they will override the defaults. my $self = { -name => (@_ == 1) ? $_[0] : undef, -file => '', -line => 0, -ldelim => '<', -rdelim => '>', @_ }; ## Initialize contents if they haven't been already my $ptree = $self->{'-ptree'} || new Pod::ParseTree(); if ( ref $ptree =~ /^(ARRAY)?$/ ) { ## We have an array-ref, or a normal scalar. Pass it as an ## an argument to the ptree-constructor $ptree = new Pod::ParseTree($1 ? [$ptree] : $ptree); } $self->{'-ptree'} = $ptree; ## Bless ourselves into the desired class and perform any initialization bless $self, $class; return $self; } ##--------------------------------------------------------------------------- sub cmd_name { (@_ > 1) and $_[0]->{'-name'} = $_[1]; return $_[0]->{'-name'}; } ## let name() be an alias for cmd_name() *name = \&cmd_name; ##--------------------------------------------------------------------------- ## Private subroutine to set the parent pointer of all the given ## children that are interior-sequences to be $self sub _set_child2parent_links { my ($self, @children) = @_; ## Make sure any sequences know who their parent is for (@children) { next unless (length and ref and ref ne 'SCALAR'); if (UNIVERSAL::isa($_, 'Pod::InteriorSequence') or UNIVERSAL::can($_, 'nested')) { $_->nested($self); } } } ## Private subroutine to unset child->parent links sub _unset_child2parent_links { my $self = shift; $self->{'-parent_sequence'} = undef; my $ptree = $self->{'-ptree'}; for (@$ptree) { next unless (length and ref and ref ne 'SCALAR'); $_->_unset_child2parent_links() if UNIVERSAL::isa($_, 'Pod::InteriorSequence'); } } ##--------------------------------------------------------------------------- sub prepend { my $self = shift; $self->{'-ptree'}->prepend(@_); _set_child2parent_links($self, @_); return $self; } ##--------------------------------------------------------------------------- sub append { my $self = shift; $self->{'-ptree'}->append(@_); _set_child2parent_links($self, @_); return $self; } ##--------------------------------------------------------------------------- sub nested { my $self = shift; (@_ == 1) and $self->{'-parent_sequence'} = shift; return $self->{'-parent_sequence'} || undef; } ##--------------------------------------------------------------------------- sub raw_text { my $self = shift; my $text = $self->{'-name'} . $self->{'-ldelim'}; for ( $self->{'-ptree'}->children ) { $text .= (ref $_) ? $_->raw_text : $_; } $text .= $self->{'-rdelim'}; return $text; } ##--------------------------------------------------------------------------- sub left_delimiter { (@_ > 1) and $_[0]->{'-ldelim'} = $_[1]; return $_[0]->{'-ldelim'}; } ## let ldelim() be an alias for left_delimiter() *ldelim = \&left_delimiter; ##--------------------------------------------------------------------------- sub right_delimiter { (@_ > 1) and $_[0]->{'-rdelim'} = $_[1]; return $_[0]->{'-rdelim'}; } ## let rdelim() be an alias for right_delimiter() *rdelim = \&right_delimiter; ##--------------------------------------------------------------------------- sub parse_tree { (@_ > 1) and $_[0]->{'-ptree'} = $_[1]; return $_[0]->{'-ptree'}; } ## let ptree() be an alias for parse_tree() *ptree = \&parse_tree; ##--------------------------------------------------------------------------- sub file_line { my @loc = ($_[0]->{'-file'} || '', $_[0]->{'-line'} || 0); return (wantarray) ? @loc : join(':', @loc); } ##--------------------------------------------------------------------------- sub DESTROY { ## We need to get rid of all child->parent pointers throughout the ## tree so their reference counts will go to zero and they can be ## garbage-collected _unset_child2parent_links(@_); } ##--------------------------------------------------------------------------- ############################################################################# package Pod::ParseTree; ##--------------------------------------------------------------------------- ##--------------------------------------------------------------------------- sub new { ## Determine if we were called via an object-ref or a classname my $this = shift; my $class = ref($this) || $this; my $self = (@_ == 1 and ref $_[0]) ? $_[0] : []; ## Bless ourselves into the desired class and perform any initialization bless $self, $class; return $self; } ##--------------------------------------------------------------------------- sub top { my $self = shift; if (@_ > 0) { @{ $self } = (@_ == 1 and ref $_[0]) ? ${ @_ } : @_; } return $self; } ## let parse_tree() & ptree() be aliases for the 'top' method *parse_tree = *ptree = \⊤ ##--------------------------------------------------------------------------- sub children { my $self = shift; if (@_ > 0) { @{ $self } = (@_ == 1 and ref $_[0]) ? ${ @_ } : @_; } return @{ $self }; } ##--------------------------------------------------------------------------- use vars qw(@ptree); ## an alias used for performance reasons sub prepend { my $self = shift; local *ptree = $self; for (@_) { next unless length; if (@ptree && !(ref $ptree[0]) && !(ref $_)) { $ptree[0] = $_ . $ptree[0]; } else { unshift @ptree, $_; } } } ##--------------------------------------------------------------------------- sub append { my $self = shift; local *ptree = $self; my $can_append = @ptree && !(ref $ptree[-1]); for (@_) { if (ref) { push @ptree, $_; } elsif(!length) { next; } elsif ($can_append) { $ptree[-1] .= $_; } else { push @ptree, $_; } } } sub raw_text { my $self = shift; my $text = ''; for ( @$self ) { $text .= (ref $_) ? $_->raw_text : $_; } return $text; } ##--------------------------------------------------------------------------- ## Private routines to set/unset child->parent links sub _unset_child2parent_links { my $self = shift; local *ptree = $self; for (@ptree) { next unless (defined and length and ref and ref ne 'SCALAR'); $_->_unset_child2parent_links() if UNIVERSAL::isa($_, 'Pod::InteriorSequence'); } } sub _set_child2parent_links { ## nothing to do, Pod::ParseTrees cant have parent pointers } sub DESTROY { ## We need to get rid of all child->parent pointers throughout the ## tree so their reference counts will go to zero and they can be ## garbage-collected _unset_child2parent_links(@_); } ############################################################################# 1; ############################################################################# # Pod/Checker.pm -- check pod documents for syntax errors # # Copyright (C) 1994-2000 by Bradford Appleton. All rights reserved. # This is free software; you can redistribute it and/or modify it under the # same terms as Perl itself. ############################################################################# package Pod::Checker; use strict; use warnings; our $VERSION = '1.73'; ## Current version of this package ############################################################################# #use diagnostics; use Carp qw(croak); use Exporter 'import'; use base qw/Pod::Simple::Methody/; our @EXPORT = qw(&podchecker); ##--------------------------------- ## Function definitions begin here ##--------------------------------- sub podchecker { my ($infile, $outfile, %options) = @_; local $_; ## Set defaults $infile ||= \*STDIN; $outfile ||= \*STDERR; ## Now create a pod checker my $checker = Pod::Checker->new(%options); ## Now check the pod document for errors $checker->parse_from_file($infile, $outfile); ## Return the number of errors found return $checker->num_errors(); } ##--------------------------------------------------------------------------- ##------------------------------- ## Method definitions begin here ##------------------------------- ################################## sub new { my $new = shift->SUPER::new(@_); $new->{'output_fh'} ||= *STDERR{IO}; # Set options my %opts = @_; $new->{'-warnings'} = defined $opts{'-warnings'} ? $opts{'-warnings'} : 1; # default on $new->{'-quiet'} = $opts{'-quiet'} || 0; # default off # Initialize number of errors/warnings $new->{'_NUM_ERRORS'} = 0; $new->{'_NUM_WARNINGS'} = 0; # 'current' also means 'most recent' in the follow comments $new->{'_thispara'} = ''; # current POD paragraph $new->{'_line'} = 0; # current line number $new->{'_head_num'} = 0; # current =head level (set to 0 to make # logic easier down the road) $new->{'_cmds_since_head'} = 0; # num of POD directives since prev. =headN $new->{'_nodes'} = []; # stack for =head/=item nodes $new->{'_fcode_stack'} = []; # stack for nested formatting codes $new->{'_fcode_pos'} = []; # stack for position in paragraph of fcodes $new->{'_begin_stack'} = []; # stack for =begins: [line #, target] $new->{'_links'} = []; # stack for hyperlinks to external entities $new->{'_internal_links'} = []; # set of linked-to internal sections $new->{'_index'} = []; # stack for text in X<>s $new->accept_targets('*'); # check all =begin/=for blocks $new->cut_handler( \&handle_pod_and_cut ); # warn if text after =cut $new->pod_handler( \&handle_pod_and_cut ); # warn if text after =pod $new->whiteline_handler( \&handle_whiteline ); # warn if whiteline $new->parse_empty_lists(1); # warn if they are empty return $new; } ################################## # Invoked as $self->poderror( @args ), or $self->poderror( {%opts}, @args ) sub poderror { my $self = shift; my %opts = (ref $_[0]) ? %{shift()} : (); ## Retrieve options chomp( my $msg = ($opts{'-msg'} || '')."@_" ); my $line = (exists $opts{'-line'}) ? " at line $opts{'-line'}" : ''; my $file = ' in file ' . ((exists $opts{'-file'}) ? $opts{'-file'} : ((defined $self->source_filename) ? $self->source_filename : "???")); unless (exists $opts{'-severity'}) { ## See if can find severity in message prefix $opts{'-severity'} = $1 if ( $msg =~ s/^\**\s*([A-Z]{3,}):\s+// ); } my $severity = (exists $opts{'-severity'}) ? "*** $opts{-severity}: " : ''; ## Increment error count and print message " ++($self->{'_NUM_ERRORS'}) if(!%opts || ($opts{-severity} && $opts{'-severity'} eq 'ERROR')); ++($self->{'_NUM_WARNINGS'}) if(!%opts || ($opts{-severity} && $opts{'-severity'} eq 'WARNING')); unless($self->{'-quiet'}) { my $out_fh = $self->{'output_fh'} || \*STDERR; print $out_fh ($severity, $msg, $line, $file, "\n") if($self->{'-warnings'} || !%opts || $opts{'-severity'} ne 'WARNING'); } } ################################## sub num_errors { return (@_ > 1) ? ($_[0]->{'_NUM_ERRORS'} = $_[1]) : $_[0]->{'_NUM_ERRORS'}; } ################################## sub num_warnings { return (@_ > 1) ? ($_[0]->{'_NUM_WARNINGS'} = $_[1]) : $_[0]->{'_NUM_WARNINGS'}; } ################################## sub name { return (@_ > 1 && $_[1]) ? ($_[0]->{'_pod_name'} = $_[1]) : $_[0]->{'_pod_name'}; } ################################## sub node { my ($self,$text) = @_; if(defined $text) { $text =~ s/\s+$//s; # strip trailing whitespace $text =~ s/\s+/ /gs; # collapse whitespace # add node, order important! push(@{$self->{'_nodes'}}, $text); # keep also a uniqueness counter $self->{'_unique_nodes'}->{$text}++ if($text !~ /^\s*$/s); return $text; } @{$self->{'_nodes'}}; } ################################## # set/return index entries of current POD sub idx { my ($self,$text) = @_; if(defined $text) { $text =~ s/\s+$//s; # strip trailing whitespace $text =~ s/\s+/ /gs; # collapse whitespace # add node, order important! push(@{$self->{'_index'}}, $text); # keep also a uniqueness counter $self->{'_unique_nodes'}->{$text}++ if($text !~ /^\s*$/s); return $text; } @{$self->{'_index'}}; } ################################## # add a hyperlink to the list of those of the current POD; returns current # list after the addition has been done sub hyperlink { my $self = shift; push(@{$self->{'_links'}}, $_[0]); return $_[0]; } sub hyperlinks { @{shift->{'_links'}}; } ################################## # override Pod::Simple's whine() and scream() to use poderror() # Note: # Ignore $self->{'no_whining'} b/c $self->{'quiet'} takes care of it in poderror # Don't bother incrementing $self->{'errors_seen'} -- it's not used # Don't bother pushing to $self->{'errata'} b/c poderror() outputs immediately # We don't need to set $self->no_errata_section(1) b/c of these overrides sub whine { my ($self, $line, $complaint) = @_; my $severity = 'ERROR'; if (0) { # XXX: Let's standardize what's a warning and what's an error. Let's not # move stuff up and down the severity tree. -- rjbs, 2013-04-12 # Convert errors in Pod::Simple that are warnings in Pod::Checker # XXX Do differently so the $complaint can be reworded without this breaking $severity = 'WARNING' if $complaint =~ /^Expected '=item .+?'$/ || $complaint =~ /^You can't have =items \(as at line .+?\) unless the first thing after the =over is an =item$/ || $complaint =~ /^You have '=item .+?' instead of the expected '=item .+?'$/; } $self->poderror({ -line => $line, -severity => $severity, -msg => $complaint }); return 1; # assume everything is peachy keen } sub scream { my ($self, $line, $complaint) = @_; $self->poderror({ -line => $line, -severity => 'ERROR', # consider making severity 'FATAL' -msg => $complaint }); return 1; } ################################## # Some helper subroutines sub _init_event { # assignments done at the start of most events $_[0]{'_thispara'} = ''; $_[0]{'_line'} = $_[1]{'start_line'}; $_[0]{'_cmds_since_head'}++; } sub _check_fcode { my ($self, $inner, $outers) = @_; # Check for an fcode inside another of the same fcode # XXX line number is the line of the start of the paragraph that the warning # is in, not the line that the warning is on. Fix this # Later versions of Pod::Simple forbid nested L<>'s return if $inner eq 'L' && $Pod::Simple::VERSION ge '3.33'; if (grep { $_ eq $inner } @$outers) { $self->poderror({ -line => $self->{'_line'}, -severity => 'WARNING', -msg => "nested commands $inner<...$inner<...>...>"}); } } ################################## sub handle_text { $_[0]{'_thispara'} .= $_[1] } # whiteline is a seemingly blank line that matches /[^\S\r\n]/ sub handle_whiteline { my ($line, $line_n, $self) = @_; $self->poderror({ -line => $line_n, -severity => 'WARNING', -msg => 'line containing nothing but whitespace in paragraph'}); } ######## Directives sub handle_pod_and_cut { my ($line, $line_n, $self) = @_; $self->{'_cmds_since_head'}++; if ($line =~ /=(pod|cut)\s+\S/) { $self->poderror({ -line => $line_n, -severity => 'ERROR', -msg => "Spurious text after =$1"}); } } sub start_Para { shift->_init_event(@_); } sub end_Para { my $self = shift; # Get the NAME of the pod document if ($self->{'_head_num'} == 1 && $self->{'_head_text'} eq 'NAME') { if ($self->{'_thispara'} =~ /^\s*(\S+?)\s*[,-]/) { $self->{'_pod_name'} = $1 unless defined $self->{'_pod_name'}; } } } sub start_Verbatim { my $self = shift; $self->_init_event(@_); if ($self->{'_head_num'} == 1 && $self->{'_head_text'} eq 'NAME') { $self->poderror({ -line => $self->{'_line'}, -severity => 'WARNING', -msg => 'Verbatim paragraph in NAME section' }); } } # Don't need an end_Verbatim # Do I need to do anything else with this? sub start_Data { shift->_init_event() } sub start_head1 { shift->start_head(1, @_) } sub start_head2 { shift->start_head(2, @_) } sub start_head3 { shift->start_head(3, @_) } sub start_head4 { shift->start_head(4, @_) } sub start_head { my $self = shift; my $h = shift; $self->_init_event(@_); my $prev_h = $self->{'_head_num'}; $self->{'_head_num'} = $h; $self->{"_count_head$h"}++; if ($h > 1 && !$self->{'_count_head'.($h-1)}) { $self->poderror({ -line => $self->{'_line'}, -severity => 'WARNING', -msg => "=head$h without preceding higher level"}); } # If this is the first =head of the doc, $prev_h is 0, thus less than $h if ($self->{'_cmds_since_head'} == 1 && $prev_h >= $h) { $self->poderror({ -line => $self->{'_line'}, -severity => 'WARNING', -msg => 'empty section in previous paragraph'}); } } sub end_head1 { shift->end_head(@_) } sub end_head2 { shift->end_head(@_) } sub end_head3 { shift->end_head(@_) } sub end_head4 { shift->end_head(@_) } sub end_head { my $self = shift; my $arg = $self->{'_thispara'}; $arg =~ s/\s+$//; $self->{'_head_text'} = $arg; $self->{'_cmds_since_head'} = 0; my $h = $self->{'_head_num'}; $self->node($arg); # remember this node if ($arg eq '') { $self->poderror({ -line => $self->{'_line'}, -severity => 'ERROR', -msg => "empty =head$h" }); } } sub start_over_bullet { shift->start_over(@_, 'bullet') } sub start_over_number { shift->start_over(@_, 'number') } sub start_over_text { shift->start_over(@_, 'definition') } sub start_over_block { shift->start_over(@_, 'block') } sub start_over_empty { my $self = shift; $self->start_over(@_, 'empty'); $self->poderror({ -line => $self->{'_line'}, -severity => 'WARNING', -msg => 'empty =over/=back block' }); } sub start_over { my $self = shift; my $type = pop; $self->_init_event(@_); } sub start_item_bullet { shift->_init_event(@_) } sub start_item_number { shift->_init_event(@_) } sub start_item_text { shift->_init_event(@_) } sub end_item_bullet { shift->end_item('bullet') } sub end_item_number { shift->end_item('number') } sub end_item_text { shift->end_item('definition') } sub end_item { my $self = shift; my $type = shift; # If there is verbatim text in this item, it will show up as part of # 'paras', and not part of '_thispara'. If the first para after this is a # verbatim one, it actually will be (part of) the contents for this item. if ( $self->{'_thispara'} eq '' && ( ! @{$self->{'paras'}} || $self->{'paras'}[0][0] !~ /Verbatim/i)) { $self->poderror({ -line => $self->{'_line'}, -severity => 'WARNING', -msg => '=item has no contents' }); } $self->node($self->{'_thispara'}); # remember this node } sub start_for { # =for and =begin directives my ($self, $flags) = @_; $self->_init_event($flags); push @{$self->{'_begin_stack'}}, [$self->{'_line'}, $flags->{'target'}]; } sub end_for { my ($self, $flags) = @_; my ($line, $target) = @{pop @{$self->{'_begin_stack'}}}; if ($flags->{'fake-closer'}) { # meaning Pod::Simple generated this =end $self->poderror({ -line => $line, -severity => 'ERROR', -msg => "=begin $target without matching =end $target" }); } } sub end_Document { # Some final error checks my $self = shift; # no POD found here $self->num_errors(-1) && return unless $self->content_seen; my %nodes; for ($self->node()) { $nodes{$_} = 1; if(/^(\S+)\s+\S/) { # we have more than one word. Use the first as a node, too. # This is used heavily in perlfunc.pod $nodes{$1} ||= 2; # derived node } } for ($self->idx()) { $nodes{$_} = 3; # index node } # XXX update unresolved internal link POD -- single word not enclosed in ""? # I don't know what I was thinking when I made the above TODO, and I don't # know what it means... for my $link (@{ $self->{'_internal_links'} }) { my ($name, $line) = @$link; unless ( $nodes{$name} ) { $self->poderror({ -line => $line, -severity => 'ERROR', -msg => "unresolved internal link '$name'"}); } } # check the internal nodes for uniqueness. This pertains to # =headX, =item and X<...> if ($self->{'-warnings'} > 1 ) { for my $node (sort keys %{ $self->{'_unique_nodes'} }) { my $count = $self->{'_unique_nodes'}{$node}; if ($count > 1) { # not unique $self->poderror({ -line => '-', -severity => 'WARNING', -msg => "multiple occurrences ($count) of link target ". "'$node'"}); } } } } ######## Formatting codes sub start_B { shift->start_fcode('B') } sub start_C { shift->start_fcode('C') } sub start_F { shift->start_fcode('F') } sub start_I { shift->start_fcode('I') } sub start_S { shift->start_fcode('S') } sub start_fcode { my ($self, $fcode) = @_; unshift @{$self->{'_fcode_stack'}}, $fcode; } sub end_B { shift->end_fcode() } sub end_C { shift->end_fcode() } sub end_F { shift->end_fcode() } sub end_I { shift->end_fcode() } sub end_S { shift->end_fcode() } sub end_fcode { my $self = shift; $self->_check_fcode(shift @{$self->{'_fcode_stack'}}, # current fcode removed $self->{'_fcode_stack'}); # previous fcodes } sub start_L { my ($self, $flags) = @_; $self->start_fcode('L'); my $link = Pod::Checker::Hyperlink->new($flags, $self); if ($link) { if ( $link->type eq 'pod' && $link->node # It's an internal-to-this-page link if no page is given, or # if the given one is to our NAME. && (! $link->page || ( $self->{'_pod_name'} && $link->page eq $self->{'_pod_name'}))) { push @{ $self->{'_internal_links'} }, [ $link->{'-raw_node'}, $link->line ]; } else { $self->hyperlink($link); } } } sub end_L { my $self = shift; $self->end_fcode(); } sub start_X { my $self = shift; $self->start_fcode('X'); # keep track of where X<> starts in the paragraph # (this is a stack so nested X<>s are handled correctly) push @{$self->{'_fcode_pos'}}, length $self->{'_thispara'}; } sub end_X { my $self = shift; # extract contents of X<> and replace with '' my $start = pop @{$self->{'_fcode_pos'}}; # start at the beginning of X<> my $end = length($self->{'_thispara'}) - $start; # end at end of X<> my $x = substr($self->{'_thispara'}, $start, $end, ''); if ($x eq "") { $self->poderror({ -line => $self->{'_line'}, -severity => 'ERROR', -msg => "An empty X<>" }); } $self->idx($x); # remember this node $self->end_fcode(); } package Pod::Checker::Hyperlink; # This class is used to represent L<> link structures, so that the individual # elements are easily accessible. It is based on code in Pod::Hyperlink sub new { my ($class, $simple_link, # The link structure returned by Pod::Simple $caller # The caller class ) = @_; my $self = +{}; bless $self, $class; $self->{'-line'} ||= $caller->{'_line'}; $self->{'-type'} ||= $simple_link->{'type'}; # Force stringification of page and node. (This expands any E<>.) $self->{'-page'} = exists $simple_link->{'to'} ? "$simple_link->{'to'}" : ""; $self->{'-node'} = exists $simple_link->{'section'} ? "$simple_link->{'section'}" : ""; # Save the unmodified node text, as the .t files are expecting the message # for internal link failures to include it (hence this preserves backward # compatibility). $self->{'-raw_node'} = $self->{'-node'}; # Remove leading/trailing white space. Pod::Simple already warns about # these, so if the only error is this, and the link is otherwise correct, # only the Pod::Simple warning will be output, avoiding unnecessary # confusion. $self->{'-page'} =~ s/ ^ \s+ //x; $self->{'-page'} =~ s/ \s+ $ //x; $self->{'-node'} =~ s/ ^ \s+ //x; $self->{'-node'} =~ s/ \s+ $ //x; # Pod::Simple warns about L<> and L< >, but not L if ($self->{'-page'} eq "" && $self->{'-node'} eq "") { $caller->poderror({ -line => $caller->{'_line'}, -severity => 'WARNING', -msg => 'empty link'}); return; } return $self; } sub line { return $_[0]->{-line}; } sub type { return $_[0]->{-type}; } sub page { return $_[0]->{-page}; } sub node { return $_[0]->{-node}; } 1 # Pod::Man -- Convert POD data to formatted *roff input. # # This module translates POD documentation into *roff markup using the man # macro set, and is intended for converting POD documents written as Unix # manual pages to manual pages that can be read by the man(1) command. It is # a replacement for the pod2man command distributed with versions of Perl # prior to 5.6. # # Perl core hackers, please note that this module is also separately # maintained outside of the Perl core as part of the podlators. Please send # me any patches at the address above in addition to sending them to the # standard Perl mailing lists. # # Written by Russ Allbery # Substantial contributions by Sean Burke # Copyright 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, # 2010, 2012, 2013, 2014, 2015, 2016, 2017 Russ Allbery # # This program is free software; you may redistribute it and/or modify it # under the same terms as Perl itself. ############################################################################## # Modules and declarations ############################################################################## package Pod::Man; use 5.006; use strict; use warnings; use subs qw(makespace); use vars qw(@ISA %ESCAPES $PREAMBLE $VERSION); use Carp qw(carp croak); use Pod::Simple (); # Conditionally import Encode and set $HAS_ENCODE if it is available. our $HAS_ENCODE; BEGIN { $HAS_ENCODE = eval { require Encode }; } @ISA = qw(Pod::Simple); $VERSION = '4.10'; # Set the debugging level. If someone has inserted a debug function into this # class already, use that. Otherwise, use any Pod::Simple debug function # that's defined, and failing that, define a debug level of 10. BEGIN { my $parent = defined (&Pod::Simple::DEBUG) ? \&Pod::Simple::DEBUG : undef; unless (defined &DEBUG) { *DEBUG = $parent || sub () { 10 }; } } # Import the ASCII constant from Pod::Simple. This is true iff we're in an # ASCII-based universe (including such things as ISO 8859-1 and UTF-8), and is # generally only false for EBCDIC. BEGIN { *ASCII = \&Pod::Simple::ASCII } # Pretty-print a data structure. Only used for debugging. BEGIN { *pretty = \&Pod::Simple::pretty } # Formatting instructions for various types of blocks. cleanup makes hyphens # hard, adds spaces between consecutive underscores, and escapes backslashes. # convert translates characters into escapes. guesswork means to apply the # transformations done by the guesswork sub. literal says to protect literal # quotes from being turned into UTF-8 quotes. By default, all transformations # are on except literal, but some elements override. # # DEFAULT specifies the default settings. All other elements should list only # those settings that they are overriding. Data indicates =for roff blocks, # which should be passed along completely verbatim. # # Formatting inherits negatively, in the sense that if the parent has turned # off guesswork, all child elements should leave it off. my %FORMATTING = ( DEFAULT => { cleanup => 1, convert => 1, guesswork => 1, literal => 0 }, Data => { cleanup => 0, convert => 0, guesswork => 0, literal => 0 }, Verbatim => { guesswork => 0, literal => 1 }, C => { guesswork => 0, literal => 1 }, X => { cleanup => 0, guesswork => 0 }, ); ############################################################################## # Object initialization ############################################################################## # Initialize the object and set various Pod::Simple options that we need. # Here, we also process any additional options passed to the constructor or # set up defaults if none were given. Note that all internal object keys are # in all-caps, reserving all lower-case object keys for Pod::Simple and user # arguments. sub new { my $class = shift; my $self = $class->SUPER::new; # Tell Pod::Simple not to handle S<> by automatically inserting  . $self->nbsp_for_S (1); # Tell Pod::Simple to keep whitespace whenever possible. if (my $preserve_whitespace = $self->can ('preserve_whitespace')) { $self->$preserve_whitespace (1); } else { $self->fullstop_space_harden (1); } # The =for and =begin targets that we accept. $self->accept_targets (qw/man MAN roff ROFF/); # Ensure that contiguous blocks of code are merged together. Otherwise, # some of the guesswork heuristics don't work right. $self->merge_text (1); # Pod::Simple doesn't do anything useful with our arguments, but we want # to put them in our object as hash keys and values. This could cause # problems if we ever clash with Pod::Simple's own internal class # variables. %$self = (%$self, @_); # Send errors to stderr if requested. if ($$self{stderr} and not $$self{errors}) { $$self{errors} = 'stderr'; } delete $$self{stderr}; # Validate the errors parameter and act on it. if (not defined $$self{errors}) { $$self{errors} = 'pod'; } if ($$self{errors} eq 'stderr' || $$self{errors} eq 'die') { $self->no_errata_section (1); $self->complain_stderr (1); if ($$self{errors} eq 'die') { $$self{complain_die} = 1; } } elsif ($$self{errors} eq 'pod') { $self->no_errata_section (0); $self->complain_stderr (0); } elsif ($$self{errors} eq 'none') { $self->no_whining (1); } else { croak (qq(Invalid errors setting: "$$self{errors}")); } delete $$self{errors}; # Degrade back to non-utf8 if Encode is not available. # # Suppress the warning message when PERL_CORE is set, indicating this is # running as part of the core Perl build. Perl builds podlators (and all # pure Perl modules) before Encode and other XS modules, so Encode won't # yet be available. Rely on the Perl core build to generate man pages # later, after all the modules are available, so that UTF-8 handling will # be correct. if ($$self{utf8} and !$HAS_ENCODE) { if (!$ENV{PERL_CORE}) { carp ('utf8 mode requested but Encode module not available,' . ' falling back to non-utf8'); } delete $$self{utf8}; } # Initialize various other internal constants based on our arguments. $self->init_fonts; $self->init_quotes; $self->init_page; # For right now, default to turning on all of the magic. $$self{MAGIC_CPP} = 1; $$self{MAGIC_EMDASH} = 1; $$self{MAGIC_FUNC} = 1; $$self{MAGIC_MANREF} = 1; $$self{MAGIC_SMALLCAPS} = 1; $$self{MAGIC_VARS} = 1; return $self; } # Translate a font string into an escape. sub toescape { (length ($_[0]) > 1 ? '\f(' : '\f') . $_[0] } # Determine which fonts the user wishes to use and store them in the object. # Regular, italic, bold, and bold-italic are constants, but the fixed width # fonts may be set by the user. Sets the internal hash key FONTS which is # used to map our internal font escapes to actual *roff sequences later. sub init_fonts { my ($self) = @_; # Figure out the fixed-width font. If user-supplied, make sure that they # are the right length. for (qw/fixed fixedbold fixeditalic fixedbolditalic/) { my $font = $$self{$_}; if (defined ($font) && (length ($font) < 1 || length ($font) > 2)) { croak qq(roff font should be 1 or 2 chars, not "$font"); } } # Set the default fonts. We can't be sure portably across different # implementations what fixed bold-italic may be called (if it's even # available), so default to just bold. $$self{fixed} ||= 'CW'; $$self{fixedbold} ||= 'CB'; $$self{fixeditalic} ||= 'CI'; $$self{fixedbolditalic} ||= 'CB'; # Set up a table of font escapes. First number is fixed-width, second is # bold, third is italic. $$self{FONTS} = { '000' => '\fR', '001' => '\fI', '010' => '\fB', '011' => '\f(BI', '100' => toescape ($$self{fixed}), '101' => toescape ($$self{fixeditalic}), '110' => toescape ($$self{fixedbold}), '111' => toescape ($$self{fixedbolditalic}) }; } # Initialize the quotes that we'll be using for C<> text. This requires some # special handling, both to parse the user parameters if given and to make # sure that the quotes will be safe against *roff. Sets the internal hash # keys LQUOTE and RQUOTE. sub init_quotes { my ($self) = (@_); # Handle the quotes option first, which sets both quotes at once. $$self{quotes} ||= '"'; if ($$self{quotes} eq 'none') { $$self{LQUOTE} = $$self{RQUOTE} = ''; } elsif (length ($$self{quotes}) == 1) { $$self{LQUOTE} = $$self{RQUOTE} = $$self{quotes}; } elsif (length ($$self{quotes}) % 2 == 0) { my $length = length ($$self{quotes}) / 2; $$self{LQUOTE} = substr ($$self{quotes}, 0, $length); $$self{RQUOTE} = substr ($$self{quotes}, $length); } else { croak(qq(Invalid quote specification "$$self{quotes}")) } # Now handle the lquote and rquote options. if (defined $$self{lquote}) { $$self{LQUOTE} = $$self{lquote} eq 'none' ? q{} : $$self{lquote}; } if (defined $$self{rquote}) { $$self{RQUOTE} = $$self{rquote} eq 'none' ? q{} : $$self{rquote}; } # Double the first quote; note that this should not be s///g as two double # quotes is represented in *roff as three double quotes, not four. Weird, # I know. $$self{LQUOTE} =~ s/\"/\"\"/; $$self{RQUOTE} =~ s/\"/\"\"/; } # Initialize the page title information and indentation from our arguments. sub init_page { my ($self) = @_; # We used to try first to get the version number from a local binary, but # we shouldn't need that any more. Get the version from the running Perl. # Work a little magic to handle subversions correctly under both the # pre-5.6 and the post-5.6 version numbering schemes. my @version = ($] =~ /^(\d+)\.(\d{3})(\d{0,3})$/); $version[2] ||= 0; $version[2] *= 10 ** (3 - length $version[2]); for (@version) { $_ += 0 } my $version = join ('.', @version); # Set the defaults for page titles and indentation if the user didn't # override anything. $$self{center} = 'User Contributed Perl Documentation' unless defined $$self{center}; $$self{release} = 'perl v' . $version unless defined $$self{release}; $$self{indent} = 4 unless defined $$self{indent}; # Double quotes in things that will be quoted. for (qw/center release/) { $$self{$_} =~ s/\"/\"\"/g if $$self{$_}; } } ############################################################################## # Core parsing ############################################################################## # This is the glue that connects the code below with Pod::Simple itself. The # goal is to convert the event stream coming from the POD parser into method # calls to handlers once the complete content of a tag has been seen. Each # paragraph or POD command will have textual content associated with it, and # as soon as all of a paragraph or POD command has been seen, that content # will be passed in to the corresponding method for handling that type of # object. The exceptions are handlers for lists, which have opening tag # handlers and closing tag handlers that will be called right away. # # The internal hash key PENDING is used to store the contents of a tag until # all of it has been seen. It holds a stack of open tags, each one # represented by a tuple of the attributes hash for the tag, formatting # options for the tag (which are inherited), and the contents of the tag. # Add a block of text to the contents of the current node, formatting it # according to the current formatting instructions as we do. sub _handle_text { my ($self, $text) = @_; DEBUG > 3 and print "== $text\n"; my $tag = $$self{PENDING}[-1]; $$tag[2] .= $self->format_text ($$tag[1], $text); } # Given an element name, get the corresponding method name. sub method_for_element { my ($self, $element) = @_; $element =~ tr/A-Z-/a-z_/; $element =~ tr/_a-z0-9//cd; return $element; } # Handle the start of a new element. If cmd_element is defined, assume that # we need to collect the entire tree for this element before passing it to the # element method, and create a new tree into which we'll collect blocks of # text and nested elements. Otherwise, if start_element is defined, call it. sub _handle_element_start { my ($self, $element, $attrs) = @_; DEBUG > 3 and print "++ $element (<", join ('> <', %$attrs), ">)\n"; my $method = $self->method_for_element ($element); # If we have a command handler, we need to accumulate the contents of the # tag before calling it. Turn off IN_NAME for any command other than # and the formatting codes so that IN_NAME isn't still set for the # first heading after the NAME heading. if ($self->can ("cmd_$method")) { DEBUG > 2 and print "<$element> starts saving a tag\n"; $$self{IN_NAME} = 0 if ($element ne 'Para' && length ($element) > 1); # How we're going to format embedded text blocks depends on the tag # and also depends on our parent tags. Thankfully, inside tags that # turn off guesswork and reformatting, nothing else can turn it back # on, so this can be strictly inherited. my $formatting = { %{ $$self{PENDING}[-1][1] || $FORMATTING{DEFAULT} }, %{ $FORMATTING{$element} || {} }, }; push (@{ $$self{PENDING} }, [ $attrs, $formatting, '' ]); DEBUG > 4 and print "Pending: [", pretty ($$self{PENDING}), "]\n"; } elsif (my $start_method = $self->can ("start_$method")) { $self->$start_method ($attrs, ''); } else { DEBUG > 2 and print "No $method start method, skipping\n"; } } # Handle the end of an element. If we had a cmd_ method for this element, # this is where we pass along the tree that we built. Otherwise, if we have # an end_ method for the element, call that. sub _handle_element_end { my ($self, $element) = @_; DEBUG > 3 and print "-- $element\n"; my $method = $self->method_for_element ($element); # If we have a command handler, pull off the pending text and pass it to # the handler along with the saved attribute hash. if (my $cmd_method = $self->can ("cmd_$method")) { DEBUG > 2 and print " stops saving a tag\n"; my $tag = pop @{ $$self{PENDING} }; DEBUG > 4 and print "Popped: [", pretty ($tag), "]\n"; DEBUG > 4 and print "Pending: [", pretty ($$self{PENDING}), "]\n"; my $text = $self->$cmd_method ($$tag[0], $$tag[2]); if (defined $text) { if (@{ $$self{PENDING} } > 1) { $$self{PENDING}[-1][2] .= $text; } else { $self->output ($text); } } } elsif (my $end_method = $self->can ("end_$method")) { $self->$end_method (); } else { DEBUG > 2 and print "No $method end method, skipping\n"; } } ############################################################################## # General formatting ############################################################################## # Format a text block. Takes a hash of formatting options and the text to # format. Currently, the only formatting options are guesswork, cleanup, and # convert, all of which are boolean. sub format_text { my ($self, $options, $text) = @_; my $guesswork = $$options{guesswork} && !$$self{IN_NAME}; my $cleanup = $$options{cleanup}; my $convert = $$options{convert}; my $literal = $$options{literal}; # Cleanup just tidies up a few things, telling *roff that the hyphens are # hard, putting a bit of space between consecutive underscores, and # escaping backslashes. Be careful not to mangle our character # translations by doing this before processing character translation. if ($cleanup) { $text =~ s/\\/\\e/g; $text =~ s/-/\\-/g; $text =~ s/_(?=_)/_\\|/g; } # Normally we do character translation, but we won't even do that in # blocks or if UTF-8 output is desired. if ($convert && !$$self{utf8} && ASCII) { $text =~ s/([^\x00-\x7F])/$ESCAPES{ord ($1)} || "X"/eg; } # Ensure that *roff doesn't convert literal quotes to UTF-8 single quotes, # but don't mess up our accept escapes. if ($literal) { $text =~ s/(?guesswork ($text); } return $text; } # Handles C<> text, deciding whether to put \*C` around it or not. This is a # whole bunch of messy heuristics to try to avoid overquoting, originally from # Barrie Slaymaker. This largely duplicates similar code in Pod::Text. sub quote_literal { my $self = shift; local $_ = shift; # A regex that matches the portion of a variable reference that's the # array or hash index, separated out just because we want to use it in # several places in the following regex. my $index = '(?: \[.*\] | \{.*\} )?'; # If in NAME section, just return an ASCII quoted string to avoid # confusing tools like whatis. return qq{"$_"} if $$self{IN_NAME}; # Check for things that we don't want to quote, and if we find any of # them, return the string with just a font change and no quoting. m{ ^\s* (?: ( [\'\`\"] ) .* \1 # already quoted | \\\*\(Aq .* \\\*\(Aq # quoted and escaped | \\?\` .* ( \' | \\\*\(Aq ) # `quoted' | \$+ [\#^]? \S $index # special ($^Foo, $") | [\$\@%&*]+ \#? [:\'\w]+ $index # plain var or func | [\$\@%&*]* [:\'\w]+ (?: -> )? \(\s*[^\s,]\s*\) # 0/1-arg func call | [-+]? ( \d[\d.]* | \.\d+ ) (?: [eE][-+]?\d+ )? # a number | 0x [a-fA-F\d]+ # a hex constant ) \s*\z }xso and return '\f(FS' . $_ . '\f(FE'; # If we didn't return, go ahead and quote the text. return '\f(FS\*(C`' . $_ . "\\*(C'\\f(FE"; } # Takes a text block to perform guesswork on. Returns the text block with # formatting codes added. This is the code that marks up various Perl # constructs and things commonly used in man pages without requiring the user # to add any explicit markup, and is applied to all non-literal text. We're # guaranteed that the text we're applying guesswork to does not contain any # *roff formatting codes. Note that the inserted font sequences must be # treated later with mapfonts or textmapfonts. # # This method is very fragile, both in the regular expressions it uses and in # the ordering of those modifications. Care and testing is required when # modifying it. sub guesswork { my $self = shift; local $_ = shift; DEBUG > 5 and print " Guesswork called on [$_]\n"; # By the time we reach this point, all hyphens will be escaped by adding a # backslash. We want to undo that escaping if they're part of regular # words and there's only a single dash, since that's a real hyphen that # *roff gets to consider a possible break point. Make sure that a dash # after the first character of a word stays non-breaking, however. # # Note that this is not user-controllable; we pretty much have to do this # transformation or *roff will mangle the output in unacceptable ways. s{ ( (?:\G|^|\s) [\(\"]* [a-zA-Z] ) ( \\- )? ( (?: [a-zA-Z\']+ \\-)+ ) ( [a-zA-Z\']+ ) (?= [\)\".?!,;:]* (?:\s|\Z|\\\ ) ) \b } { my ($prefix, $hyphen, $main, $suffix) = ($1, $2, $3, $4); $hyphen ||= ''; $main =~ s/\\-/-/g; $prefix . $hyphen . $main . $suffix; }egx; # Translate "--" into a real em-dash if it's used like one. This means # that it's either surrounded by whitespace, it follows a regular word, or # it occurs between two regular words. if ($$self{MAGIC_EMDASH}) { s{ (\s) \\-\\- (\s) } { $1 . '\*(--' . $2 }egx; s{ (\b[a-zA-Z]+) \\-\\- (\s|\Z|[a-zA-Z]+\b) } { $1 . '\*(--' . $2 }egx; } # Make words in all-caps a little bit smaller; they look better that way. # However, we don't want to change Perl code (like @ARGV), nor do we want # to fix the MIME in MIME-Version since it looks weird with the # full-height V. # # We change only a string of all caps (2) either at the beginning of the # line or following regular punctuation (like quotes) or whitespace (1), # and followed by either similar punctuation, an em-dash, or the end of # the line (3). # # Allow the text we're changing to small caps to include double quotes, # commas, newlines, and periods as long as it doesn't otherwise interrupt # the string of small caps and still fits the criteria. This lets us turn # entire warranty disclaimers in man page output into small caps. if ($$self{MAGIC_SMALLCAPS}) { s{ ( ^ | [\s\(\"\'\`\[\{<>] | \\[ ] ) # (1) ( [A-Z] [A-Z] (?: \s? [/A-Z+:\d_\$&] | \\- | \s? [.,\"] )* ) # (2) (?= [\s>\}\]\(\)\'\".?!,;] | \\*\(-- | \\[ ] | $ ) # (3) } { $1 . '\s-1' . $2 . '\s0' }egx; } # Note that from this point forward, we have to adjust for \s-1 and \s-0 # strings inserted around things that we've made small-caps if later # transforms should work on those strings. # Embolden functions in the form func(), including functions that are in # all capitals, but don't embolden if there's anything between the parens. # The function must start with an alphabetic character or underscore and # then consist of word characters or colons. if ($$self{MAGIC_FUNC}) { s{ ( \b | \\s-1 ) ( [A-Za-z_] ([:\w] | \\s-?[01])+ \(\) ) } { $1 . '\f(BS' . $2 . '\f(BE' }egx; } # Change references to manual pages to put the page name in bold but # the number in the regular font, with a thin space between the name and # the number. Only recognize func(n) where func starts with an alphabetic # character or underscore and contains only word characters, periods (for # configuration file man pages), or colons, and n is a single digit, # optionally followed by some number of lowercase letters. Note that this # does not recognize man page references like perl(l) or socket(3SOCKET). if ($$self{MAGIC_MANREF}) { s{ ( \b | \\s-1 ) (? 5 and print " Guesswork returning [$_]\n"; return $_; } ############################################################################## # Output ############################################################################## # When building up the *roff code, we don't use real *roff fonts. Instead, we # embed font codes of the form \f([SE] where is one of B, I, or # F, S stands for start, and E stands for end. This method turns these into # the right start and end codes. # # We add this level of complexity because the old pod2man didn't get code like # B else> right; after I<> it switched back to normal text rather # than bold. We take care of this by using variables that state whether bold, # italic, or fixed are turned on as a combined pointer to our current font # sequence, and set each to the number of current nestings of start tags for # that font. # # \fP changes to the previous font, but only one previous font is kept. We # don't know what the outside level font is; normally it's R, but if we're # inside a heading it could be something else. So arrange things so that the # outside font is always the "previous" font and end with \fP instead of \fR. # Idea from Zack Weinberg. sub mapfonts { my ($self, $text) = @_; my ($fixed, $bold, $italic) = (0, 0, 0); my %magic = (F => \$fixed, B => \$bold, I => \$italic); my $last = '\fR'; $text =~ s< \\f\((.)(.) > < my $sequence = ''; my $f; if ($last ne '\fR') { $sequence = '\fP' } ${ $magic{$1} } += ($2 eq 'S') ? 1 : -1; $f = $$self{FONTS}{ ($fixed && 1) . ($bold && 1) . ($italic && 1) }; if ($f eq $last) { ''; } else { if ($f ne '\fR') { $sequence .= $f } $last = $f; $sequence; } >gxe; return $text; } # Unfortunately, there is a bug in Solaris 2.6 nroff (not present in GNU # groff) where the sequence \fB\fP\f(CW\fP leaves the font set to B rather # than R, presumably because \f(CW doesn't actually do a font change. To work # around this, use a separate textmapfonts for text blocks where the default # font is always R and only use the smart mapfonts for headings. sub textmapfonts { my ($self, $text) = @_; my ($fixed, $bold, $italic) = (0, 0, 0); my %magic = (F => \$fixed, B => \$bold, I => \$italic); $text =~ s< \\f\((.)(.) > < ${ $magic{$1} } += ($2 eq 'S') ? 1 : -1; $$self{FONTS}{ ($fixed && 1) . ($bold && 1) . ($italic && 1) }; >gxe; return $text; } # Given a command and a single argument that may or may not contain double # quotes, handle double-quote formatting for it. If there are no double # quotes, just return the command followed by the argument in double quotes. # If there are double quotes, use an if statement to test for nroff, and for # nroff output the command followed by the argument in double quotes with # embedded double quotes doubled. For other formatters, remap paired double # quotes to LQUOTE and RQUOTE. sub switchquotes { my ($self, $command, $text, $extra) = @_; $text =~ s/\\\*\([LR]\"/\"/g; # We also have to deal with \*C` and \*C', which are used to add the # quotes around C<> text, since they may expand to " and if they do this # confuses the .SH macros and the like no end. Expand them ourselves. # Also separate troff from nroff if there are any fixed-width fonts in use # to work around problems with Solaris nroff. my $c_is_quote = ($$self{LQUOTE} =~ /\"/) || ($$self{RQUOTE} =~ /\"/); my $fixedpat = join '|', @{ $$self{FONTS} }{'100', '101', '110', '111'}; $fixedpat =~ s/\\/\\\\/g; $fixedpat =~ s/\(/\\\(/g; if ($text =~ m/\"/ || $text =~ m/$fixedpat/) { $text =~ s/\"/\"\"/g; my $nroff = $text; my $troff = $text; $troff =~ s/\"\"([^\"]*)\"\"/\`\`$1\'\'/g; if ($c_is_quote and $text =~ m/\\\*\(C[\'\`]/) { $nroff =~ s/\\\*\(C\`/$$self{LQUOTE}/g; $nroff =~ s/\\\*\(C\'/$$self{RQUOTE}/g; $troff =~ s/\\\*\(C[\'\`]//g; } $nroff = qq("$nroff") . ($extra ? " $extra" : ''); $troff = qq("$troff") . ($extra ? " $extra" : ''); # Work around the Solaris nroff bug where \f(CW\fP leaves the font set # to Roman rather than the actual previous font when used in headings. # troff output may still be broken, but at least we can fix nroff by # just switching the font changes to the non-fixed versions. my $font_end = "(?:\\f[PR]|\Q$$self{FONTS}{100}\E)"; $nroff =~ s/\Q$$self{FONTS}{100}\E(.*?)\\f([PR])/$1/g; $nroff =~ s/\Q$$self{FONTS}{101}\E(.*?)$font_end/\\fI$1\\fP/g; $nroff =~ s/\Q$$self{FONTS}{110}\E(.*?)$font_end/\\fB$1\\fP/g; $nroff =~ s/\Q$$self{FONTS}{111}\E(.*?)$font_end/\\f\(BI$1\\fP/g; # Now finally output the command. Bother with .ie only if the nroff # and troff output aren't the same. if ($nroff ne $troff) { return ".ie n $command $nroff\n.el $command $troff\n"; } else { return "$command $nroff\n"; } } else { $text = qq("$text") . ($extra ? " $extra" : ''); return "$command $text\n"; } } # Protect leading quotes and periods against interpretation as commands. Also # protect anything starting with a backslash, since it could expand or hide # something that *roff would interpret as a command. This is overkill, but # it's much simpler than trying to parse *roff here. sub protect { my ($self, $text) = @_; $text =~ s/^([.\'\\])/\\&$1/mg; return $text; } # Make vertical whitespace if NEEDSPACE is set, appropriate to the indentation # level the situation. This function is needed since in *roff one has to # create vertical whitespace after paragraphs and between some things, but # other macros create their own whitespace. Also close out a sequence of # repeated =items, since calling makespace means we're about to begin the item # body. sub makespace { my ($self) = @_; $self->output (".PD\n") if $$self{ITEMS} > 1; $$self{ITEMS} = 0; $self->output ($$self{INDENT} > 0 ? ".Sp\n" : ".PP\n") if $$self{NEEDSPACE}; } # Output any pending index entries, and optionally an index entry given as an # argument. Support multiple index entries in X<> separated by slashes, and # strip special escapes from index entries. sub outindex { my ($self, $section, $index) = @_; my @entries = map { split m%\s*/\s*% } @{ $$self{INDEX} }; return unless ($section || @entries); # We're about to output all pending entries, so clear our pending queue. $$self{INDEX} = []; # Build the output. Regular index entries are marked Xref, and headings # pass in their own section. Undo some *roff formatting on headings. my @output; if (@entries) { push @output, [ 'Xref', join (' ', @entries) ]; } if ($section) { $index =~ s/\\-/-/g; $index =~ s/\\(?:s-?\d|.\(..|.)//g; push @output, [ $section, $index ]; } # Print out the .IX commands. for (@output) { my ($type, $entry) = @$_; $entry =~ s/\s+/ /g; $entry =~ s/\"/\"\"/g; $entry =~ s/\\/\\\\/g; $self->output (".IX $type " . '"' . $entry . '"' . "\n"); } } # Output some text, without any additional changes. sub output { my ($self, @text) = @_; if ($$self{ENCODE}) { print { $$self{output_fh} } Encode::encode ('UTF-8', join ('', @text)); } else { print { $$self{output_fh} } @text; } } ############################################################################## # Document initialization ############################################################################## # Handle the start of the document. Here we handle empty documents, as well # as setting up our basic macros in a preamble and building the page title. sub start_document { my ($self, $attrs) = @_; if ($$attrs{contentless} && !$$self{ALWAYS_EMIT_SOMETHING}) { DEBUG and print "Document is contentless\n"; $$self{CONTENTLESS} = 1; } else { delete $$self{CONTENTLESS}; } # When UTF-8 output is set, check whether our output file handle already # has a PerlIO encoding layer set. If it does not, we'll need to encode # our output before printing it (handled in the output() sub). Wrap the # check in an eval to handle versions of Perl without PerlIO. # # PerlIO::get_layers still requires its argument be a glob, so coerce the # file handle to a glob. $$self{ENCODE} = 0; if ($$self{utf8}) { $$self{ENCODE} = 1; eval { my @options = (output => 1, details => 1); my @layers = PerlIO::get_layers (*{$$self{output_fh}}, @options); if ($layers[-1] & PerlIO::F_UTF8 ()) { $$self{ENCODE} = 0; } } } # Determine information for the preamble and then output it unless the # document was content-free. if (!$$self{CONTENTLESS}) { my ($name, $section); if (defined $$self{name}) { $name = $$self{name}; $section = $$self{section} || 1; } else { ($name, $section) = $self->devise_title; } my $date = defined($$self{date}) ? $$self{date} : $self->devise_date; $self->preamble ($name, $section, $date) unless $self->bare_output or DEBUG > 9; } # Initialize a few per-document variables. $$self{INDENT} = 0; # Current indentation level. $$self{INDENTS} = []; # Stack of indentations. $$self{INDEX} = []; # Index keys waiting to be printed. $$self{IN_NAME} = 0; # Whether processing the NAME section. $$self{ITEMS} = 0; # The number of consecutive =items. $$self{ITEMTYPES} = []; # Stack of =item types, one per list. $$self{SHIFTWAIT} = 0; # Whether there is a shift waiting. $$self{SHIFTS} = []; # Stack of .RS shifts. $$self{PENDING} = [[]]; # Pending output. } # Handle the end of the document. This handles dying on POD errors, since # Pod::Parser currently doesn't. Otherwise, does nothing but print out a # final comment at the end of the document under debugging. sub end_document { my ($self) = @_; if ($$self{complain_die} && $self->errors_seen) { croak ("POD document had syntax errors"); } return if $self->bare_output; return if ($$self{CONTENTLESS} && !$$self{ALWAYS_EMIT_SOMETHING}); $self->output (q(.\" [End document]) . "\n") if DEBUG; } # Try to figure out the name and section from the file name and return them as # a list, returning an empty name and section 1 if we can't find any better # information. Uses File::Basename and File::Spec as necessary. sub devise_title { my ($self) = @_; my $name = $self->source_filename || ''; my $section = $$self{section} || 1; $section = 3 if (!$$self{section} && $name =~ /\.pm\z/i); $name =~ s/\.p(od|[lm])\z//i; # If Pod::Parser gave us an IO::File reference as the source file name, # convert that to the empty string as well. Then, if we don't have a # valid name, convert it to STDIN. # # In podlators 4.00 through 4.07, this also produced a warning, but that # was surprising to a lot of programs that had expected to be able to pipe # POD through pod2man without specifying the name. In the name of # backward compatibility, just quietly set STDIN as the page title. if ($name =~ /^IO::File(?:=\w+)\(0x[\da-f]+\)$/i) { $name = ''; } if ($name eq '') { $name = 'STDIN'; } # If the section isn't 3, then the name defaults to just the basename of # the file. if ($section !~ /^3/) { require File::Basename; $name = uc File::Basename::basename ($name); } else { require File::Spec; my ($volume, $dirs, $file) = File::Spec->splitpath ($name); # Otherwise, assume we're dealing with a module. We want to figure # out the full module name from the path to the file, but we don't # want to include too much of the path into the module name. Lose # anything up to the first of: # # */lib/*perl*/ standard or site_perl module # */*perl*/lib/ from -Dprefix=/opt/perl # */*perl*/ random module hierarchy # # Also strip off a leading site, site_perl, or vendor_perl component, # any OS-specific component, and any version number component, and # strip off an initial component of "lib" or "blib/lib" since that's # what ExtUtils::MakeMaker creates. # # splitdir requires at least File::Spec 0.8. my @dirs = File::Spec->splitdir ($dirs); if (@dirs) { my $cut = 0; my $i; for ($i = 0; $i < @dirs; $i++) { if ($dirs[$i] =~ /perl/) { $cut = $i + 1; $cut++ if ($dirs[$i + 1] && $dirs[$i + 1] eq 'lib'); last; } } if ($cut > 0) { splice (@dirs, 0, $cut); shift @dirs if ($dirs[0] =~ /^(site|vendor)(_perl)?$/); shift @dirs if ($dirs[0] =~ /^[\d.]+$/); shift @dirs if ($dirs[0] =~ /^(.*-$^O|$^O-.*|$^O)$/); } shift @dirs if $dirs[0] eq 'lib'; splice (@dirs, 0, 2) if ($dirs[0] eq 'blib' && $dirs[1] eq 'lib'); } # Remove empty directories when building the module name; they # occur too easily on Unix by doubling slashes. $name = join ('::', (grep { $_ ? $_ : () } @dirs), $file); } return ($name, $section); } # Determine the modification date and return that, properly formatted in ISO # format. # # If POD_MAN_DATE is set, that overrides anything else. This can be used for # reproducible generation of the same file even if the input file timestamps # are unpredictable or the POD comes from standard input. # # Otherwise, if SOURCE_DATE_EPOCH is set and can be parsed as seconds since # the UNIX epoch, base the timestamp on that. See # # # Otherwise, use the modification date of the input if we can stat it. Be # aware that Pod::Simple returns the stringification of the file handle as # source_filename for input from a file handle, so we'll stat some random ref # string in that case. If that fails, instead use the current time. # # $self - Pod::Man object, used to get the source file # # Returns: YYYY-MM-DD date suitable for the left-hand footer sub devise_date { my ($self) = @_; # If POD_MAN_DATE is set, always use it. if (defined($ENV{POD_MAN_DATE})) { return $ENV{POD_MAN_DATE}; } # If SOURCE_DATE_EPOCH is set and can be parsed, use that. my $time; if (defined($ENV{SOURCE_DATE_EPOCH}) && $ENV{SOURCE_DATE_EPOCH} !~ /\D/) { $time = $ENV{SOURCE_DATE_EPOCH}; } # Otherwise, get the input filename and try to stat it. If that fails, # use the current time. if (!defined $time) { my $input = $self->source_filename; if ($input) { $time = (stat($input))[9] || time(); } else { $time = time(); } } # Can't use POSIX::strftime(), which uses Fcntl, because MakeMaker uses # this and it has to work in the core which can't load dynamic libraries. # Use gmtime instead of localtime so that the generated man page does not # depend on the local time zone setting and is more reproducible my ($year, $month, $day) = (gmtime($time))[5,4,3]; return sprintf("%04d-%02d-%02d", $year + 1900, $month + 1, $day); } # Print out the preamble and the title. The meaning of the arguments to .TH # unfortunately vary by system; some systems consider the fourth argument to # be a "source" and others use it as a version number. Generally it's just # presented as the left-side footer, though, so it doesn't matter too much if # a particular system gives it another interpretation. # # The order of date and release used to be reversed in older versions of this # module, but this order is correct for both Solaris and Linux. sub preamble { my ($self, $name, $section, $date) = @_; my $preamble = $self->preamble_template (!$$self{utf8}); # Build the index line and make sure that it will be syntactically valid. my $index = "$name $section"; $index =~ s/\"/\"\"/g; # If name or section contain spaces, quote them (section really never # should, but we may as well be cautious). for ($name, $section) { if (/\s/) { s/\"/\"\"/g; $_ = '"' . $_ . '"'; } } # Double quotes in date, since it will be quoted. $date =~ s/\"/\"\"/g; # Substitute into the preamble the configuration options. $preamble =~ s/\@CFONT\@/$$self{fixed}/; $preamble =~ s/\@LQUOTE\@/$$self{LQUOTE}/; $preamble =~ s/\@RQUOTE\@/$$self{RQUOTE}/; chomp $preamble; # Get the version information. my $version = $self->version_report; # Finally output everything. $self->output (<<"----END OF HEADER----"); .\\" Automatically generated by $version .\\" .\\" Standard preamble: .\\" ======================================================================== $preamble .\\" ======================================================================== .\\" .IX Title "$index" .TH $name $section "$date" "$$self{release}" "$$self{center}" .\\" For nroff, turn off justification. Always turn off hyphenation; it makes .\\" way too many mistakes in technical documents. .if n .ad l .nh ----END OF HEADER---- $self->output (".\\\" [End of preamble]\n") if DEBUG; } ############################################################################## # Text blocks ############################################################################## # Handle a basic block of text. The only tricky part of this is if this is # the first paragraph of text after an =over, in which case we have to change # indentations for *roff. sub cmd_para { my ($self, $attrs, $text) = @_; my $line = $$attrs{start_line}; # Output the paragraph. We also have to handle =over without =item. If # there's an =over without =item, SHIFTWAIT will be set, and we need to # handle creation of the indent here. Add the shift to SHIFTS so that it # will be cleaned up on =back. $self->makespace; if ($$self{SHIFTWAIT}) { $self->output (".RS $$self{INDENT}\n"); push (@{ $$self{SHIFTS} }, $$self{INDENT}); $$self{SHIFTWAIT} = 0; } # Add the line number for debugging, but not in the NAME section just in # case the comment would confuse apropos. $self->output (".\\\" [At source line $line]\n") if defined ($line) && DEBUG && !$$self{IN_NAME}; # Force exactly one newline at the end and strip unwanted trailing # whitespace at the end, but leave "\ " backslashed space from an S< > at # the end of a line. Reverse the text first, to avoid having to scan the # entire paragraph. $text = reverse $text; $text =~ s/\A\s*?(?= \\|\S|\z)/\n/; $text = reverse $text; # Output the paragraph. $self->output ($self->protect ($self->textmapfonts ($text))); $self->outindex; $$self{NEEDSPACE} = 1; return ''; } # Handle a verbatim paragraph. Put a null token at the beginning of each line # to protect against commands and wrap in .Vb/.Ve (which we define in our # prelude). sub cmd_verbatim { my ($self, $attrs, $text) = @_; # Ignore an empty verbatim paragraph. return unless $text =~ /\S/; # Force exactly one newline at the end and strip unwanted trailing # whitespace at the end. Reverse the text first, to avoid having to scan # the entire paragraph. $text = reverse $text; $text =~ s/\A\s*/\n/; $text = reverse $text; # Get a count of the number of lines before the first blank line, which # we'll pass to .Vb as its parameter. This tells *roff to keep that many # lines together. We don't want to tell *roff to keep huge blocks # together. my @lines = split (/\n/, $text); my $unbroken = 0; for (@lines) { last if /^\s*$/; $unbroken++; } $unbroken = 10 if ($unbroken > 12 && !$$self{MAGIC_VNOPAGEBREAK_LIMIT}); # Prepend a null token to each line. $text =~ s/^/\\&/gm; # Output the results. $self->makespace; $self->output (".Vb $unbroken\n$text.Ve\n"); $$self{NEEDSPACE} = 1; return ''; } # Handle literal text (produced by =for and similar constructs). Just output # it with the minimum of changes. sub cmd_data { my ($self, $attrs, $text) = @_; $text =~ s/^\n+//; $text =~ s/\n{0,2}$/\n/; $self->output ($text); return ''; } ############################################################################## # Headings ############################################################################## # Common code for all headings. This is called before the actual heading is # output. It returns the cleaned up heading text (putting the heading all on # one line) and may do other things, like closing bad =item blocks. sub heading_common { my ($self, $text, $line) = @_; $text =~ s/\s+$//; $text =~ s/\s*\n\s*/ /g; # This should never happen; it means that we have a heading after =item # without an intervening =back. But just in case, handle it anyway. if ($$self{ITEMS} > 1) { $$self{ITEMS} = 0; $self->output (".PD\n"); } # Output the current source line. $self->output ( ".\\\" [At source line $line]\n" ) if defined ($line) && DEBUG; return $text; } # First level heading. We can't output .IX in the NAME section due to a bug # in some versions of catman, so don't output a .IX for that section. .SH # already uses small caps, so remove \s0 and \s-1. Maintain IN_NAME as # appropriate. sub cmd_head1 { my ($self, $attrs, $text) = @_; $text =~ s/\\s-?\d//g; $text = $self->heading_common ($text, $$attrs{start_line}); my $isname = ($text eq 'NAME' || $text =~ /\(NAME\)/); $self->output ($self->switchquotes ('.SH', $self->mapfonts ($text))); $self->outindex ('Header', $text) unless $isname; $$self{NEEDSPACE} = 0; $$self{IN_NAME} = $isname; return ''; } # Second level heading. sub cmd_head2 { my ($self, $attrs, $text) = @_; $text = $self->heading_common ($text, $$attrs{start_line}); $self->output ($self->switchquotes ('.SS', $self->mapfonts ($text))); $self->outindex ('Subsection', $text); $$self{NEEDSPACE} = 0; return ''; } # Third level heading. *roff doesn't have this concept, so just put the # heading in italics as a normal paragraph. sub cmd_head3 { my ($self, $attrs, $text) = @_; $text = $self->heading_common ($text, $$attrs{start_line}); $self->makespace; $self->output ($self->textmapfonts ('\f(IS' . $text . '\f(IE') . "\n"); $self->outindex ('Subsection', $text); $$self{NEEDSPACE} = 1; return ''; } # Fourth level heading. *roff doesn't have this concept, so just put the # heading as a normal paragraph. sub cmd_head4 { my ($self, $attrs, $text) = @_; $text = $self->heading_common ($text, $$attrs{start_line}); $self->makespace; $self->output ($self->textmapfonts ($text) . "\n"); $self->outindex ('Subsection', $text); $$self{NEEDSPACE} = 1; return ''; } ############################################################################## # Formatting codes ############################################################################## # All of the formatting codes that aren't handled internally by the parser, # other than L<> and X<>. sub cmd_b { return $_[0]->{IN_NAME} ? $_[2] : '\f(BS' . $_[2] . '\f(BE' } sub cmd_i { return $_[0]->{IN_NAME} ? $_[2] : '\f(IS' . $_[2] . '\f(IE' } sub cmd_f { return $_[0]->{IN_NAME} ? $_[2] : '\f(IS' . $_[2] . '\f(IE' } sub cmd_c { return $_[0]->quote_literal ($_[2]) } # Index entries are just added to the pending entries. sub cmd_x { my ($self, $attrs, $text) = @_; push (@{ $$self{INDEX} }, $text); return ''; } # Links reduce to the text that we're given, wrapped in angle brackets if it's # a URL, followed by the URL. We take an option to suppress the URL if anchor # text is given. We need to format the "to" value of the link before # comparing it to the text since we may escape hyphens. sub cmd_l { my ($self, $attrs, $text) = @_; if ($$attrs{type} eq 'url') { my $to = $$attrs{to}; if (defined $to) { my $tag = $$self{PENDING}[-1]; $to = $self->format_text ($$tag[1], $to); } if (not defined ($to) or $to eq $text) { return "<$text>"; } elsif ($$self{nourls}) { return $text; } else { return "$text <$$attrs{to}>"; } } else { return $text; } } ############################################################################## # List handling ############################################################################## # Handle the beginning of an =over block. Takes the type of the block as the # first argument, and then the attr hash. This is called by the handlers for # the four different types of lists (bullet, number, text, and block). sub over_common_start { my ($self, $type, $attrs) = @_; my $line = $$attrs{start_line}; my $indent = $$attrs{indent}; DEBUG > 3 and print " Starting =over $type (line $line, indent ", ($indent || '?'), "\n"; # Find the indentation level. unless (defined ($indent) && $indent =~ /^[-+]?\d{1,4}\s*$/) { $indent = $$self{indent}; } # If we've gotten multiple indentations in a row, we need to emit the # pending indentation for the last level that we saw and haven't acted on # yet. SHIFTS is the stack of indentations that we've actually emitted # code for. if (@{ $$self{SHIFTS} } < @{ $$self{INDENTS} }) { $self->output (".RS $$self{INDENT}\n"); push (@{ $$self{SHIFTS} }, $$self{INDENT}); } # Now, do record-keeping. INDENTS is a stack of indentations that we've # seen so far, and INDENT is the current level of indentation. ITEMTYPES # is a stack of list types that we've seen. push (@{ $$self{INDENTS} }, $$self{INDENT}); push (@{ $$self{ITEMTYPES} }, $type); $$self{INDENT} = $indent + 0; $$self{SHIFTWAIT} = 1; } # End an =over block. Takes no options other than the class pointer. # Normally, once we close a block and therefore remove something from INDENTS, # INDENTS will now be longer than SHIFTS, indicating that we also need to emit # *roff code to close the indent. This isn't *always* true, depending on the # circumstance. If we're still inside an indentation, we need to emit another # .RE and then a new .RS to unconfuse *roff. sub over_common_end { my ($self) = @_; DEBUG > 3 and print " Ending =over\n"; $$self{INDENT} = pop @{ $$self{INDENTS} }; pop @{ $$self{ITEMTYPES} }; # If we emitted code for that indentation, end it. if (@{ $$self{SHIFTS} } > @{ $$self{INDENTS} }) { $self->output (".RE\n"); pop @{ $$self{SHIFTS} }; } # If we're still in an indentation, *roff will have now lost track of the # right depth of that indentation, so fix that. if (@{ $$self{INDENTS} } > 0) { $self->output (".RE\n"); $self->output (".RS $$self{INDENT}\n"); } $$self{NEEDSPACE} = 1; $$self{SHIFTWAIT} = 0; } # Dispatch the start and end calls as appropriate. sub start_over_bullet { my $s = shift; $s->over_common_start ('bullet', @_) } sub start_over_number { my $s = shift; $s->over_common_start ('number', @_) } sub start_over_text { my $s = shift; $s->over_common_start ('text', @_) } sub start_over_block { my $s = shift; $s->over_common_start ('block', @_) } sub end_over_bullet { $_[0]->over_common_end } sub end_over_number { $_[0]->over_common_end } sub end_over_text { $_[0]->over_common_end } sub end_over_block { $_[0]->over_common_end } # The common handler for all item commands. Takes the type of the item, the # attributes, and then the text of the item. # # Emit an index entry for anything that's interesting, but don't emit index # entries for things like bullets and numbers. Newlines in an item title are # turned into spaces since *roff can't handle them embedded. sub item_common { my ($self, $type, $attrs, $text) = @_; my $line = $$attrs{start_line}; DEBUG > 3 and print " $type item (line $line): $text\n"; # Clean up the text. We want to end up with two variables, one ($text) # which contains any body text after taking out the item portion, and # another ($item) which contains the actual item text. $text =~ s/\s+$//; my ($item, $index); if ($type eq 'bullet') { $item = "\\\(bu"; $text =~ s/\n*$/\n/; } elsif ($type eq 'number') { $item = $$attrs{number} . '.'; } else { $item = $text; $item =~ s/\s*\n\s*/ /g; $text = ''; $index = $item if ($item =~ /\w/); } # Take care of the indentation. If shifts and indents are equal, close # the top shift, since we're about to create an indentation with .IP. # Also output .PD 0 to turn off spacing between items if this item is # directly following another one. We only have to do that once for a # whole chain of items so do it for the second item in the change. Note # that makespace is what undoes this. if (@{ $$self{SHIFTS} } == @{ $$self{INDENTS} }) { $self->output (".RE\n"); pop @{ $$self{SHIFTS} }; } $self->output (".PD 0\n") if ($$self{ITEMS} == 1); # Now, output the item tag itself. $item = $self->textmapfonts ($item); $self->output ($self->switchquotes ('.IP', $item, $$self{INDENT})); $$self{NEEDSPACE} = 0; $$self{ITEMS}++; $$self{SHIFTWAIT} = 0; # If body text for this item was included, go ahead and output that now. if ($text) { $text =~ s/\s*$/\n/; $self->makespace; $self->output ($self->protect ($self->textmapfonts ($text))); $$self{NEEDSPACE} = 1; } $self->outindex ($index ? ('Item', $index) : ()); } # Dispatch the item commands to the appropriate place. sub cmd_item_bullet { my $self = shift; $self->item_common ('bullet', @_) } sub cmd_item_number { my $self = shift; $self->item_common ('number', @_) } sub cmd_item_text { my $self = shift; $self->item_common ('text', @_) } sub cmd_item_block { my $self = shift; $self->item_common ('block', @_) } ############################################################################## # Backward compatibility ############################################################################## # Reset the underlying Pod::Simple object between calls to parse_from_file so # that the same object can be reused to convert multiple pages. sub parse_from_file { my $self = shift; $self->reinit; # Fake the old cutting option to Pod::Parser. This fiddles with internal # Pod::Simple state and is quite ugly; we need a better approach. if (ref ($_[0]) eq 'HASH') { my $opts = shift @_; if (defined ($$opts{-cutting}) && !$$opts{-cutting}) { $$self{in_pod} = 1; $$self{last_was_blank} = 1; } } # Do the work. my $retval = $self->SUPER::parse_from_file (@_); # Flush output, since Pod::Simple doesn't do this. Ideally we should also # close the file descriptor if we had to open one, but we can't easily # figure this out. my $fh = $self->output_fh (); my $oldfh = select $fh; my $oldflush = $|; $| = 1; print $fh ''; $| = $oldflush; select $oldfh; return $retval; } # Pod::Simple failed to provide this backward compatibility function, so # implement it ourselves. File handles are one of the inputs that # parse_from_file supports. sub parse_from_filehandle { my $self = shift; return $self->parse_from_file (@_); } # Pod::Simple's parse_file doesn't set output_fh. Wrap the call and do so # ourself unless it was already set by the caller, since our documentation has # always said that this should work. sub parse_file { my ($self, $in) = @_; unless (defined $$self{output_fh}) { $self->output_fh (\*STDOUT); } return $self->SUPER::parse_file ($in); } # Do the same for parse_lines, just to be polite. Pod::Simple's man page # implies that the caller is responsible for setting this, but I don't see any # reason not to set a default. sub parse_lines { my ($self, @lines) = @_; unless (defined $$self{output_fh}) { $self->output_fh (\*STDOUT); } return $self->SUPER::parse_lines (@lines); } # Likewise for parse_string_document. sub parse_string_document { my ($self, $doc) = @_; unless (defined $$self{output_fh}) { $self->output_fh (\*STDOUT); } return $self->SUPER::parse_string_document ($doc); } ############################################################################## # Translation tables ############################################################################## # The following table is adapted from Tom Christiansen's pod2man. It assumes # that the standard preamble has already been printed, since that's what # defines all of the accent marks. We really want to do something better than # this when *roff actually supports other character sets itself, since these # results are pretty poor. # # This only works in an ASCII world. What to do in a non-ASCII world is very # unclear -- hopefully we can assume UTF-8 and just leave well enough alone. @ESCAPES{0xA0 .. 0xFF} = ( "\\ ", undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, "\\%", undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, "A\\*`", "A\\*'", "A\\*^", "A\\*~", "A\\*:", "A\\*o", "\\*(Ae", "C\\*,", "E\\*`", "E\\*'", "E\\*^", "E\\*:", "I\\*`", "I\\*'", "I\\*^", "I\\*:", "\\*(D-", "N\\*~", "O\\*`", "O\\*'", "O\\*^", "O\\*~", "O\\*:", undef, "O\\*/", "U\\*`", "U\\*'", "U\\*^", "U\\*:", "Y\\*'", "\\*(Th", "\\*8", "a\\*`", "a\\*'", "a\\*^", "a\\*~", "a\\*:", "a\\*o", "\\*(ae", "c\\*,", "e\\*`", "e\\*'", "e\\*^", "e\\*:", "i\\*`", "i\\*'", "i\\*^", "i\\*:", "\\*(d-", "n\\*~", "o\\*`", "o\\*'", "o\\*^", "o\\*~", "o\\*:", undef, "o\\*/" , "u\\*`", "u\\*'", "u\\*^", "u\\*:", "y\\*'", "\\*(th", "y\\*:", ) if ASCII; ############################################################################## # Premable ############################################################################## # The following is the static preamble which starts all *roff output we # generate. Most is static except for the font to use as a fixed-width font, # which is designed by @CFONT@, and the left and right quotes to use for C<> # text, designated by @LQOUTE@ and @RQUOTE@. However, the second part, which # defines the accent marks, is only used if $escapes is set to true. sub preamble_template { my ($self, $accents) = @_; my $preamble = <<'----END OF PREAMBLE----'; .de Sp \" Vertical space (when we can't use .PP) .if t .sp .5v .if n .sp .. .de Vb \" Begin verbatim text .ft @CFONT@ .nf .ne \\$1 .. .de Ve \" End verbatim text .ft R .fi .. .\" Set up some character translations and predefined strings. \*(-- will .\" give an unbreakable dash, \*(PI will give pi, \*(L" will give a left .\" double quote, and \*(R" will give a right double quote. \*(C+ will .\" give a nicer C++. Capital omega is used to do unbreakable dashes and .\" therefore won't be available. \*(C` and \*(C' expand to `' in nroff, .\" nothing in troff, for use with C<>. .tr \(*W- .ds C+ C\v'-.1v'\h'-1p'\s-2+\h'-1p'+\s0\v'.1v'\h'-1p' .ie n \{\ . ds -- \(*W- . ds PI pi . if (\n(.H=4u)&(1m=24u) .ds -- \(*W\h'-12u'\(*W\h'-12u'-\" diablo 10 pitch . if (\n(.H=4u)&(1m=20u) .ds -- \(*W\h'-12u'\(*W\h'-8u'-\" diablo 12 pitch . ds L" "" . ds R" "" . ds C` @LQUOTE@ . ds C' @RQUOTE@ 'br\} .el\{\ . ds -- \|\(em\| . ds PI \(*p . ds L" `` . ds R" '' . ds C` . ds C' 'br\} .\" .\" Escape single quotes in literal strings from groff's Unicode transform. .ie \n(.g .ds Aq \(aq .el .ds Aq ' .\" .\" If the F register is >0, we'll generate index entries on stderr for .\" titles (.TH), headers (.SH), subsections (.SS), items (.Ip), and index .\" entries marked with X<> in POD. Of course, you'll have to process the .\" output yourself in some meaningful fashion. .\" .\" Avoid warning from groff about undefined register 'F'. .de IX .. .nr rF 0 .if \n(.g .if rF .nr rF 1 .if (\n(rF:(\n(.g==0)) \{\ . if \nF \{\ . de IX . tm Index:\\$1\t\\n%\t"\\$2" .. . if !\nF==2 \{\ . nr % 0 . nr F 2 . \} . \} .\} .rr rF ----END OF PREAMBLE---- #'# for cperl-mode if ($accents) { $preamble .= <<'----END OF PREAMBLE----' .\" .\" Accent mark definitions (@(#)ms.acc 1.5 88/02/08 SMI; from UCB 4.2). .\" Fear. Run. Save yourself. No user-serviceable parts. . \" fudge factors for nroff and troff .if n \{\ . ds #H 0 . ds #V .8m . ds #F .3m . ds #[ \f1 . ds #] \fP .\} .if t \{\ . ds #H ((1u-(\\\\n(.fu%2u))*.13m) . ds #V .6m . ds #F 0 . ds #[ \& . ds #] \& .\} . \" simple accents for nroff and troff .if n \{\ . ds ' \& . ds ` \& . ds ^ \& . ds , \& . ds ~ ~ . ds / .\} .if t \{\ . ds ' \\k:\h'-(\\n(.wu*8/10-\*(#H)'\'\h"|\\n:u" . ds ` \\k:\h'-(\\n(.wu*8/10-\*(#H)'\`\h'|\\n:u' . ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'^\h'|\\n:u' . ds , \\k:\h'-(\\n(.wu*8/10)',\h'|\\n:u' . ds ~ \\k:\h'-(\\n(.wu-\*(#H-.1m)'~\h'|\\n:u' . ds / \\k:\h'-(\\n(.wu*8/10-\*(#H)'\z\(sl\h'|\\n:u' .\} . \" troff and (daisy-wheel) nroff accents .ds : \\k:\h'-(\\n(.wu*8/10-\*(#H+.1m+\*(#F)'\v'-\*(#V'\z.\h'.2m+\*(#F'.\h'|\\n:u'\v'\*(#V' .ds 8 \h'\*(#H'\(*b\h'-\*(#H' .ds o \\k:\h'-(\\n(.wu+\w'\(de'u-\*(#H)/2u'\v'-.3n'\*(#[\z\(de\v'.3n'\h'|\\n:u'\*(#] .ds d- \h'\*(#H'\(pd\h'-\w'~'u'\v'-.25m'\f2\(hy\fP\v'.25m'\h'-\*(#H' .ds D- D\\k:\h'-\w'D'u'\v'-.11m'\z\(hy\v'.11m'\h'|\\n:u' .ds th \*(#[\v'.3m'\s+1I\s-1\v'-.3m'\h'-(\w'I'u*2/3)'\s-1o\s+1\*(#] .ds Th \*(#[\s+2I\s-2\h'-\w'I'u*3/5'\v'-.3m'o\v'.3m'\*(#] .ds ae a\h'-(\w'a'u*4/10)'e .ds Ae A\h'-(\w'A'u*4/10)'E . \" corrections for vroff .if v .ds ~ \\k:\h'-(\\n(.wu*9/10-\*(#H)'\s-2\u~\d\s+2\h'|\\n:u' .if v .ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'\v'-.4m'^\v'.4m'\h'|\\n:u' . \" for low resolution devices (crt and lpr) .if \n(.H>23 .if \n(.V>19 \ \{\ . ds : e . ds 8 ss . ds o a . ds d- d\h'-1'\(ga . ds D- D\h'-1'\(hy . ds th \o'bp' . ds Th \o'LP' . ds ae ae . ds Ae AE .\} .rm #[ #] #H #V #F C ----END OF PREAMBLE---- #`# for cperl-mode } return $preamble; } ############################################################################## # Module return value and documentation ############################################################################## 1; __END__ use 5.006; # we use some open(X, "<", $y) syntax package Pod::Perldoc; use strict; use warnings; use Config '%Config'; use Fcntl; # for sysopen use File::Basename qw(basename); use File::Spec::Functions qw(catfile catdir splitdir); use vars qw($VERSION @Pagers $Bindir $Pod2man $Temp_Files_Created $Temp_File_Lifetime ); $VERSION = '3.2801'; #.......................................................................... BEGIN { # Make a DEBUG constant very first thing... unless(defined &DEBUG) { if(($ENV{'PERLDOCDEBUG'} || '') =~ m/^(\d+)/) { # untaint eval("sub DEBUG () {$1}"); die "WHAT? Couldn't eval-up a DEBUG constant!? $@" if $@; } else { *DEBUG = sub () {0}; } } } use Pod::Perldoc::GetOptsOO; # uses the DEBUG. use Carp qw(croak carp); # these are also in BaseTo, which I don't want to inherit sub debugging { my $self = shift; ( defined(&Pod::Perldoc::DEBUG) and &Pod::Perldoc::DEBUG() ) } sub debug { my( $self, @messages ) = @_; return unless $self->debugging; print STDERR map { "DEBUG : $_" } @messages; } sub warn { my( $self, @messages ) = @_; carp( join "\n", @messages, '' ); } sub die { my( $self, @messages ) = @_; croak( join "\n", @messages, '' ); } #.......................................................................... sub TRUE () {1} sub FALSE () {return} sub BE_LENIENT () {1} BEGIN { *is_vms = $^O eq 'VMS' ? \&TRUE : \&FALSE unless defined &is_vms; *is_mswin32 = $^O eq 'MSWin32' ? \&TRUE : \&FALSE unless defined &is_mswin32; *is_dos = $^O eq 'dos' ? \&TRUE : \&FALSE unless defined &is_dos; *is_os2 = $^O eq 'os2' ? \&TRUE : \&FALSE unless defined &is_os2; *is_cygwin = $^O eq 'cygwin' ? \&TRUE : \&FALSE unless defined &is_cygwin; *is_linux = $^O eq 'linux' ? \&TRUE : \&FALSE unless defined &is_linux; *is_hpux = $^O =~ m/hpux/ ? \&TRUE : \&FALSE unless defined &is_hpux; *is_amigaos = $^O eq 'amigaos' ? \&TRUE : \&FALSE unless defined &is_amigaos; } $Temp_File_Lifetime ||= 60 * 60 * 24 * 5; # If it's older than five days, it's quite unlikely # that anyone's still looking at it!! # (Currently used only by the MSWin cleanup routine) #.......................................................................... { my $pager = $Config{'pager'}; push @Pagers, $pager if -x (split /\s+/, $pager)[0] or __PACKAGE__->is_vms; } $Bindir = $Config{'scriptdirexp'}; $Pod2man = "pod2man" . ( $Config{'versiononly'} ? $Config{'version'} : '' ); # End of class-init stuff # ########################################################################### # # Option accessors... foreach my $subname (map "opt_$_", split '', q{mhlDriFfXqnTdULva}) { no strict 'refs'; *$subname = do{ use strict 'refs'; sub () { shift->_elem($subname, @_) } }; } # And these are so that GetOptsOO knows they take options: sub opt_a_with { shift->_elem('opt_a', @_) } sub opt_f_with { shift->_elem('opt_f', @_) } sub opt_q_with { shift->_elem('opt_q', @_) } sub opt_d_with { shift->_elem('opt_d', @_) } sub opt_L_with { shift->_elem('opt_L', @_) } sub opt_v_with { shift->_elem('opt_v', @_) } sub opt_w_with { # Specify an option for the formatter subclass my($self, $value) = @_; if($value =~ m/^([-_a-zA-Z][-_a-zA-Z0-9]*)(?:[=\:](.*?))?$/s) { my $option = $1; my $option_value = defined($2) ? $2 : "TRUE"; $option =~ tr/\-/_/s; # tolerate "foo-bar" for "foo_bar" $self->add_formatter_option( $option, $option_value ); } else { $self->warn( qq("$value" isn't a good formatter option name. I'm ignoring it!\n ) ); } return; } sub opt_M_with { # specify formatter class name(s) my($self, $classes) = @_; return unless defined $classes and length $classes; DEBUG > 4 and print "Considering new formatter classes -M$classes\n"; my @classes_to_add; foreach my $classname (split m/[,;]+/s, $classes) { next unless $classname =~ m/\S/; if( $classname =~ m/^(\w+(::\w+)+)$/s ) { # A mildly restrictive concept of what modulenames are valid. push @classes_to_add, $1; # untaint } else { $self->warn( qq("$classname" isn't a valid classname. Ignoring.\n) ); } } unshift @{ $self->{'formatter_classes'} }, @classes_to_add; DEBUG > 3 and print( "Adding @classes_to_add to the list of formatter classes, " . "making them @{ $self->{'formatter_classes'} }.\n" ); return; } sub opt_V { # report version and exit print join '', "Perldoc v$VERSION, under perl v$] for $^O", (defined(&Win32::BuildNumber) and defined &Win32::BuildNumber()) ? (" (win32 build ", &Win32::BuildNumber(), ")") : (), (chr(65) eq 'A') ? () : " (non-ASCII)", "\n", ; exit; } sub opt_t { # choose plaintext as output format my $self = shift; $self->opt_o_with('text') if @_ and $_[0]; return $self->_elem('opt_t', @_); } sub opt_u { # choose raw pod as output format my $self = shift; $self->opt_o_with('pod') if @_ and $_[0]; return $self->_elem('opt_u', @_); } sub opt_n_with { # choose man as the output format, and specify the proggy to run my $self = shift; $self->opt_o_with('man') if @_ and $_[0]; $self->_elem('opt_n', @_); } sub opt_o_with { # "o" for output format my($self, $rest) = @_; return unless defined $rest and length $rest; if($rest =~ m/^(\w+)$/s) { $rest = $1; #untaint } else { $self->warn( qq("$rest" isn't a valid output format. Skipping.\n") ); return; } $self->aside("Noting \"$rest\" as desired output format...\n"); # Figure out what class(es) that could actually mean... my @classes; foreach my $prefix ("Pod::Perldoc::To", "Pod::Simple::", "Pod::") { # Messy but smart: foreach my $stem ( $rest, # Yes, try it first with the given capitalization "\L$rest", "\L\u$rest", "\U$rest" # And then try variations ) { $self->aside("Considering $prefix$stem\n"); push @classes, $prefix . $stem; } # Tidier, but misses too much: #push @classes, $prefix . ucfirst(lc($rest)); } $self->opt_M_with( join ";", @classes ); return; } ########################################################################### # % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % sub run { # to be called by the "perldoc" executable my $class = shift; if(DEBUG > 3) { print "Parameters to $class\->run:\n"; my @x = @_; while(@x) { $x[1] = '' unless defined $x[1]; $x[1] = "@{$x[1]}" if ref( $x[1] ) eq 'ARRAY'; print " [$x[0]] => [$x[1]]\n"; splice @x,0,2; } print "\n"; } return $class -> new(@_) -> process() || 0; } # % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % ########################################################################### sub new { # yeah, nothing fancy my $class = shift; my $new = bless {@_}, (ref($class) || $class); DEBUG > 1 and print "New $class object $new\n"; $new->init(); $new; } #.......................................................................... sub aside { # If we're in -D or DEBUG mode, say this. my $self = shift; if( DEBUG or $self->opt_D ) { my $out = join( '', DEBUG ? do { my $callsub = (caller(1))[3]; my $package = quotemeta(__PACKAGE__ . '::'); $callsub =~ s/^$package/'/os; # the o is justified, as $package really won't change. $callsub . ": "; } : '', @_, ); if(DEBUG) { print $out } else { print STDERR $out } } return; } #.......................................................................... sub usage { my $self = shift; $self->warn( "@_\n" ) if @_; # Erase evidence of previous errors (if any), so exit status is simple. $! = 0; CORE::die( <debug( "The value in $0 is a symbolic link to $link\n" ); } my $basename = basename( $0 ); $self->debug( "\$0 is [$0]\nbasename is [$basename]\n" ); # possible name forms # perldoc # perldoc-v5.14 # perldoc-5.14 # perldoc-5.14.2 # perlvar # an alias mentioned in Camel 3 { my( $untainted ) = $basename =~ m/( \A perl (?: doc | func | faq | help | op | toc | var # Camel 3 ) (?: -? v? \d+ \. \d+ (?:\. \d+)? )? # possible version (?: \. (?: bat | exe | com ) )? # possible extension \z ) /x; $self->debug($untainted); return $untainted if $untainted; } $self->warn(<<"HERE"); You called the perldoc command with a name that I didn't recognize. This might mean that someone is tricking you into running a program you don't intend to use, but it also might mean that you created your own link to perldoc. I think your program name is [$basename]. I'll allow this if the filename only has [a-zA-Z0-9._-]. HERE { my( $untainted ) = $basename =~ m/( \A [a-zA-Z0-9._-]+ \z )/x; $self->debug($untainted); return $untainted if $untainted; } $self->die(<<"HERE"); I think that your name for perldoc is potentially unsafe, so I'm going to disallow it. I'd rather you be safe than sorry. If you intended to use the name I'm disallowing, please tell the maintainers about it. Write to: Pod-Perldoc\@rt.cpan.org HERE } #.......................................................................... sub usage_brief { my $self = shift; my $program_name = $self->program_name; CORE::die( <<"EOUSAGE" ); Usage: $program_name [-hVriDtumUFXlT] [-n nroffer_program] [-d output_filename] [-o output_format] [-M FormatterModule] [-w formatter_option:option_value] [-L translation_code] PageName|ModuleName|ProgramName Examples: $program_name -f PerlFunc $program_name -q FAQKeywords $program_name -v PerlVar $program_name -a PerlAPI The -h option prints more help. Also try "$program_name perldoc" to get acquainted with the system. [Perldoc v$VERSION] EOUSAGE } #.......................................................................... sub pagers { @{ shift->{'pagers'} } } #.......................................................................... sub _elem { # handy scalar meta-accessor: shift->_elem("foo", @_) if(@_ > 2) { return $_[0]{ $_[1] } = $_[2] } else { return $_[0]{ $_[1] } } } #.......................................................................... ########################################################################### # # Init formatter switches, and start it off with __bindir and all that # other stuff that ToMan.pm needs. # sub init { my $self = shift; # Make sure creat()s are neither too much nor too little eval { umask(0077) }; # doubtless someone has no mask if ( $] < 5.008 ) { $self->aside("Your old perl doesn't have proper unicode support."); } else { # http://www.perl.com/pub/2012/04/perlunicookbook-decode-argv-as-utf8.html # Decode command line arguments as UTF-8. See RT#98906 for example problem. use Encode qw(decode_utf8); @ARGV = map { decode_utf8($_, 1) } @ARGV; } $self->{'args'} ||= \@ARGV; $self->{'found'} ||= []; $self->{'temp_file_list'} ||= []; $self->{'target'} = undef; $self->init_formatter_class_list; $self->{'pagers' } = [@Pagers] unless exists $self->{'pagers'}; $self->{'bindir' } = $Bindir unless exists $self->{'bindir'}; $self->{'pod2man'} = $Pod2man unless exists $self->{'pod2man'}; $self->{'search_path'} = [ ] unless exists $self->{'search_path'}; push @{ $self->{'formatter_switches'} = [] }, ( # Yeah, we could use a hashref, but maybe there's some class where options # have to be ordered; so we'll use an arrayref. [ '__bindir' => $self->{'bindir' } ], [ '__pod2man' => $self->{'pod2man'} ], ); DEBUG > 3 and printf "Formatter switches now: [%s]\n", join ' ', map "[@$_]", @{ $self->{'formatter_switches'} }; $self->{'translators'} = []; $self->{'extra_search_dirs'} = []; return; } #.......................................................................... sub init_formatter_class_list { my $self = shift; $self->{'formatter_classes'} ||= []; # Remember, no switches have been read yet, when # we've started this routine. $self->opt_M_with('Pod::Perldoc::ToPod'); # the always-there fallthru $self->opt_o_with('text'); return; } #.......................................................................... sub process { # if this ever returns, its retval will be used for exit(RETVAL) my $self = shift; DEBUG > 1 and print " Beginning process.\n"; DEBUG > 1 and print " Args: @{$self->{'args'}}\n\n"; if(DEBUG > 3) { print "Object contents:\n"; my @x = %$self; while(@x) { $x[1] = '' unless defined $x[1]; $x[1] = "@{$x[1]}" if ref( $x[1] ) eq 'ARRAY'; print " [$x[0]] => [$x[1]]\n"; splice @x,0,2; } print "\n"; } # TODO: make it deal with being invoked as various different things # such as perlfaq". return $self->usage_brief unless @{ $self->{'args'} }; $self->options_reading; $self->pagers_guessing; $self->aside(sprintf "$0 => %s v%s\n", ref($self), $self->VERSION); $self->drop_privs_maybe unless ($self->opt_U || $self->opt_F); $self->options_processing; # Hm, we have @pages and @found, but we only really act on one # file per call, with the exception of the opt_q hack, and with # -l things $self->aside("\n"); my @pages; $self->{'pages'} = \@pages; if( $self->opt_f) { @pages = qw(perlfunc perlop) } elsif( $self->opt_q) { @pages = ("perlfaq1" .. "perlfaq9") } elsif( $self->opt_v) { @pages = ("perlvar") } elsif( $self->opt_a) { @pages = ("perlapi") } else { @pages = @{$self->{'args'}}; # @pages = __FILE__ # if @pages == 1 and $pages[0] eq 'perldoc'; } return $self->usage_brief unless @pages; $self->find_good_formatter_class(); $self->formatter_sanity_check(); $self->maybe_extend_searchpath(); # for when we're apparently in a module or extension directory my @found = $self->grand_search_init(\@pages); exit ($self->is_vms ? 98962 : 1) unless @found; if ($self->opt_l and not $self->opt_q ) { DEBUG and print "We're in -l mode, so byebye after this:\n"; print join("\n", @found), "\n"; return; } $self->tweak_found_pathnames(\@found); $self->assert_closing_stdout; return $self->page_module_file(@found) if $self->opt_m; DEBUG > 2 and print "Found: [@found]\n"; return $self->render_and_page(\@found); } #.......................................................................... { my( %class_seen, %class_loaded ); sub find_good_formatter_class { my $self = $_[0]; my @class_list = @{ $self->{'formatter_classes'} || [] }; $self->die( "WHAT? Nothing in the formatter class list!?" ) unless @class_list; local @INC = @INC; pop @INC if $INC[-1] eq '.'; my $good_class_found; foreach my $c (@class_list) { DEBUG > 4 and print "Trying to load $c...\n"; if($class_loaded{$c}) { DEBUG > 4 and print "OK, the already-loaded $c it is!\n"; $good_class_found = $c; last; } if($class_seen{$c}) { DEBUG > 4 and print "I've tried $c before, and it's no good. Skipping.\n"; next; } $class_seen{$c} = 1; if( $c->can('parse_from_file') ) { DEBUG > 4 and print "Interesting, the formatter class $c is already loaded!\n"; } elsif( ( $self->is_os2 or $self->is_mswin32 or $self->is_dos or $self->is_os2) # the always case-insensitive filesystems and $class_seen{lc("~$c")}++ ) { DEBUG > 4 and print "We already used something quite like \"\L$c\E\", so no point using $c\n"; # This avoids redefining the package. } else { DEBUG > 4 and print "Trying to eval 'require $c'...\n"; local $^W = $^W; if(DEBUG() or $self->opt_D) { # feh, let 'em see it } else { $^W = 0; # The average user just has no reason to be seeing # $^W-suppressible warnings from the require! } eval "require $c"; if($@) { DEBUG > 4 and print "Couldn't load $c: $!\n"; next; } } if( $c->can('parse_from_file') ) { DEBUG > 4 and print "Settling on $c\n"; my $v = $c->VERSION; $v = ( defined $v and length $v ) ? " version $v" : ''; $self->aside("Formatter class $c$v successfully loaded!\n"); $good_class_found = $c; last; } else { DEBUG > 4 and print "Class $c isn't a formatter?! Skipping.\n"; } } $self->die( "Can't find any loadable formatter class in @class_list?!\nAborting" ) unless $good_class_found; $self->{'formatter_class'} = $good_class_found; $self->aside("Will format with the class $good_class_found\n"); return; } } #.......................................................................... sub formatter_sanity_check { my $self = shift; my $formatter_class = $self->{'formatter_class'} || $self->die( "NO FORMATTER CLASS YET!?" ); if(!$self->opt_T # so -T can FORCE sending to STDOUT and $formatter_class->can('is_pageable') and !$formatter_class->is_pageable and !$formatter_class->can('page_for_perldoc') ) { my $ext = ($formatter_class->can('output_extension') && $formatter_class->output_extension ) || ''; $ext = ".$ext" if length $ext; my $me = $self->program_name; $self->die( "When using Perldoc to format with $formatter_class, you have to\n" . "specify -T or -dsomefile$ext\n" . "See `$me perldoc' for more information on those switches.\n" ) ; } } #.......................................................................... sub render_and_page { my($self, $found_list) = @_; $self->maybe_generate_dynamic_pod($found_list); my($out, $formatter) = $self->render_findings($found_list); if($self->opt_d) { printf "Perldoc (%s) output saved to %s\n", $self->{'formatter_class'} || ref($self), $out; print "But notice that it's 0 bytes long!\n" unless -s $out; } elsif( # Allow the formatter to "page" itself, if it wants. $formatter->can('page_for_perldoc') and do { $self->aside("Going to call $formatter\->page_for_perldoc(\"$out\")\n"); if( $formatter->page_for_perldoc($out, $self) ) { $self->aside("page_for_perldoc returned true, so NOT paging with $self.\n"); 1; } else { $self->aside("page_for_perldoc returned false, so paging with $self instead.\n"); ''; } } ) { # Do nothing, since the formatter has "paged" it for itself. } else { # Page it normally (internally) if( -s $out ) { # Usual case: $self->page($out, $self->{'output_to_stdout'}, $self->pagers); } else { # Odd case: $self->aside("Skipping $out (from $$found_list[0] " . "via $$self{'formatter_class'}) as it is 0-length.\n"); push @{ $self->{'temp_file_list'} }, $out; $self->unlink_if_temp_file($out); } } $self->after_rendering(); # any extra cleanup or whatever return; } #.......................................................................... sub options_reading { my $self = shift; if( defined $ENV{"PERLDOC"} and length $ENV{"PERLDOC"} ) { require Text::ParseWords; $self->aside("Noting env PERLDOC setting of $ENV{'PERLDOC'}\n"); # Yes, appends to the beginning unshift @{ $self->{'args'} }, Text::ParseWords::shellwords( $ENV{"PERLDOC"} ) ; DEBUG > 1 and print " Args now: @{$self->{'args'}}\n\n"; } else { DEBUG > 1 and print " Okay, no PERLDOC setting in ENV.\n"; } DEBUG > 1 and print " Args right before switch processing: @{$self->{'args'}}\n"; Pod::Perldoc::GetOptsOO::getopts( $self, $self->{'args'}, 'YES' ) or return $self->usage; DEBUG > 1 and print " Args after switch processing: @{$self->{'args'}}\n"; return $self->usage if $self->opt_h; return; } #.......................................................................... sub options_processing { my $self = shift; if ($self->opt_X) { my $podidx = "$Config{'archlib'}/pod.idx"; $podidx = "" unless -f $podidx && -r _ && -M _ <= 7; $self->{'podidx'} = $podidx; } $self->{'output_to_stdout'} = 1 if $self->opt_T or ! -t STDOUT; $self->options_sanity; # This used to set a default, but that's now moved into any # formatter that cares to have a default. if( $self->opt_n ) { $self->add_formatter_option( '__nroffer' => $self->opt_n ); } # Get language from PERLDOC_POD2 environment variable if ( ! $self->opt_L && $ENV{PERLDOC_POD2} ) { if ( $ENV{PERLDOC_POD2} eq '1' ) { $self->_elem('opt_L',(split(/\_/, $ENV{LC_ALL} || $ENV{LC_LANG} || $ENV{LANG}))[0] ); } else { $self->_elem('opt_L', $ENV{PERLDOC_POD2}); } }; # Adjust for using translation packages $self->add_translator(split(/\s+/,$self->opt_L)) if $self->opt_L; return; } #.......................................................................... sub options_sanity { my $self = shift; # The opts-counting stuff interacts quite badly with # the $ENV{"PERLDOC"} stuff. I.e., if I have $ENV{"PERLDOC"} # set to -t, and I specify -u on the command line, I don't want # to be hectored at that -u and -t don't make sense together. #my $opts = grep $_ && 1, # yes, the count of the set ones # $self->opt_t, $self->opt_u, $self->opt_m, $self->opt_l #; # #$self->usage("only one of -t, -u, -m or -l") if $opts > 1; # Any sanity-checking need doing here? # But does not make sense to set either -f or -q in $ENV{"PERLDOC"} if( $self->opt_f or $self->opt_q or $self->opt_a) { my $count; $count++ if $self->opt_f; $count++ if $self->opt_q; $count++ if $self->opt_a; $self->usage("Only one of -f or -q or -a") if $count > 1; $self->warn( "Perldoc is meant for reading one file at a time.\n", "So these parameters are being ignored: ", join(' ', @{$self->{'args'}}), "\n" ) if @{$self->{'args'}} } return; } #.......................................................................... sub grand_search_init { my($self, $pages, @found) = @_; foreach (@$pages) { if (/^http(s)?:\/\//) { require HTTP::Tiny; require File::Temp; my $response = HTTP::Tiny->new->get($_); if ($response->{success}) { my ($fh, $filename) = File::Temp::tempfile(UNLINK => 1); $fh->print($response->{content}); push @found, $filename; ($self->{podnames}{$filename} = m{.*/([^/#?]+)} ? uc $1 : "UNKNOWN") =~ s/\.P(?:[ML]|OD)\z//; } else { print STDERR "No " . ($self->opt_m ? "module" : "documentation") . " found for \"$_\".\n"; if ( /^https/ ) { print STDERR "You may need an SSL library (such as IO::Socket::SSL) for that URL.\n"; } } next; } if ($self->{'podidx'} && open(PODIDX, $self->{'podidx'})) { my $searchfor = catfile split '::', $_; $self->aside( "Searching for '$searchfor' in $self->{'podidx'}\n" ); local $_; while () { chomp; push(@found, $_) if m,/$searchfor(?:\.(?:pod|pm))?\z,i; } close(PODIDX) or $self->die( "Can't close $$self{'podidx'}: $!" ); next; } $self->aside( "Searching for $_\n" ); if ($self->opt_F) { next unless -r; push @found, $_ if $self->opt_l or $self->opt_m or $self->containspod($_); next; } my @searchdirs; # prepend extra search directories (including language specific) push @searchdirs, @{ $self->{'extra_search_dirs'} }; # We must look both in @INC for library modules and in $bindir # for executables, like h2xs or perldoc itself. push @searchdirs, ($self->{'bindir'}, @{$self->{search_path}}, @INC); unless ($self->opt_m) { if ($self->is_vms) { my($i,$trn); for ($i = 0; $trn = $ENV{'DCL$PATH;'.$i}; $i++) { push(@searchdirs,$trn); } push(@searchdirs,'perl_root:[lib.pods]') # installed pods } else { push(@searchdirs, grep(-d, split($Config{path_sep}, $ENV{'PATH'}))); } } my @files = $self->searchfor(0,$_,@searchdirs); if (@files) { $self->aside( "Found as @files\n" ); } # add "perl" prefix, so "perldoc foo" may find perlfoo.pod elsif (BE_LENIENT and !/\W/ and @files = $self->searchfor(0, "perl$_", @searchdirs)) { $self->aside( "Loosely found as @files\n" ); } else { # no match, try recursive search @searchdirs = grep(!/^\.\z/s,@INC); @files= $self->searchfor(1,$_,@searchdirs) if $self->opt_r; if (@files) { $self->aside( "Loosely found as @files\n" ); } else { print STDERR "No " . ($self->opt_m ? "module" : "documentation") . " found for \"$_\".\n"; if ( @{ $self->{'found'} } ) { print STDERR "However, try\n"; my $me = $self->program_name; for my $dir (@{ $self->{'found'} }) { opendir(DIR, $dir) or $self->die( "opendir $dir: $!" ); while (my $file = readdir(DIR)) { next if ($file =~ /^\./s); $file =~ s/\.(pm|pod)\z//; # XXX: badfs print STDERR "\t$me $_\::$file\n"; } closedir(DIR) or $self->die( "closedir $dir: $!" ); } } } } push(@found,@files); } return @found; } #.......................................................................... sub maybe_generate_dynamic_pod { my($self, $found_things) = @_; my @dynamic_pod; $self->search_perlapi($found_things, \@dynamic_pod) if $self->opt_a; $self->search_perlfunc($found_things, \@dynamic_pod) if $self->opt_f; $self->search_perlvar($found_things, \@dynamic_pod) if $self->opt_v; $self->search_perlfaqs($found_things, \@dynamic_pod) if $self->opt_q; if( ! $self->opt_f and ! $self->opt_q and ! $self->opt_v and ! $self->opt_a) { DEBUG > 4 and print "That's a non-dynamic pod search.\n"; } elsif ( @dynamic_pod ) { $self->aside("Hm, I found some Pod from that search!\n"); my ($buffd, $buffer) = $self->new_tempfile('pod', 'dyn'); if ( $] >= 5.008 && $self->opt_L ) { binmode($buffd, ":encoding(UTF-8)"); print $buffd "=encoding utf8\n\n"; } push @{ $self->{'temp_file_list'} }, $buffer; # I.e., it MIGHT be deleted at the end. my $in_list = !$self->not_dynamic && $self->opt_f || $self->opt_v || $self->opt_a; print $buffd "=over 8\n\n" if $in_list; print $buffd @dynamic_pod or $self->die( "Can't print $buffer: $!" ); print $buffd "=back\n" if $in_list; close $buffd or $self->die( "Can't close $buffer: $!" ); @$found_things = $buffer; # Yes, so found_things never has more than one thing in # it, by time we leave here $self->add_formatter_option('__filter_nroff' => 1); } else { @$found_things = (); $self->aside("I found no Pod from that search!\n"); } return; } #.......................................................................... sub not_dynamic { my ($self,$value) = @_; $self->{__not_dynamic} = $value if @_ == 2; return $self->{__not_dynamic}; } #.......................................................................... sub add_formatter_option { # $self->add_formatter_option('key' => 'value'); my $self = shift; push @{ $self->{'formatter_switches'} }, [ @_ ] if @_; DEBUG > 3 and printf "Formatter switches now: [%s]\n", join ' ', map "[@$_]", @{ $self->{'formatter_switches'} }; return; } #......................................................................... sub new_translator { # $tr = $self->new_translator($lang); my $self = shift; my $lang = shift; local @INC = @INC; pop @INC if $INC[-1] eq '.'; my $pack = 'POD2::' . uc($lang); eval "require $pack"; if ( !$@ && $pack->can('new') ) { return $pack->new(); } eval { require POD2::Base }; return if $@; return POD2::Base->new({ lang => $lang }); } #......................................................................... sub add_translator { # $self->add_translator($lang); my $self = shift; for my $lang (@_) { my $tr = $self->new_translator($lang); if ( defined $tr ) { push @{ $self->{'translators'} }, $tr; push @{ $self->{'extra_search_dirs'} }, $tr->pod_dirs; $self->aside( "translator for '$lang' loaded\n" ); } else { # non-installed or bad translator package $self->warn( "Perldoc cannot load translator package for '$lang': ignored\n" ); } } return; } #.......................................................................... sub open_fh { my ($self, $op, $path) = @_; open my $fh, $op, $path or $self->die("Couldn't open $path: $!"); return $fh; } sub set_encoding { my ($self, $fh, $encoding) = @_; if ( $encoding =~ /utf-?8/i ) { $encoding = ":encoding(UTF-8)"; } else { $encoding = ":encoding($encoding)"; } if ( $] < 5.008 ) { $self->aside("Your old perl doesn't have proper unicode support."); } else { binmode($fh, $encoding); } return $fh; } sub search_perlvar { my($self, $found_things, $pod) = @_; my $opt = $self->opt_v; if ( $opt !~ /^ (?: [\@\%\$]\S+ | [A-Z]\w* ) $/x ) { CORE::die( "'$opt' does not look like a Perl variable\n" ); } DEBUG > 2 and print "Search: @$found_things\n"; my $perlvar = shift @$found_things; my $fh = $self->open_fh("<", $perlvar); if ( $opt ne '$0' && $opt =~ /^\$\d+$/ ) { # handle $1, $2, ... $opt = '$>'; } my $search_re = quotemeta($opt); DEBUG > 2 and print "Going to perlvar-scan for $search_re in $perlvar\n"; # Skip introduction local $_; my $enc; while (<$fh>) { $enc = $1 if /^=encoding\s+(\S+)/; last if /^=over 8/; } $fh = $self->set_encoding($fh, $enc) if $enc; # Look for our variable my $found = 0; my $inheader = 1; my $inlist = 0; while (<$fh>) { last if /^=head2 Error Indicators/; # \b at the end of $` and friends borks things! if ( m/^=item\s+$search_re\s/ ) { $found = 1; } elsif (/^=item/) { last if $found && !$inheader && !$inlist; } elsif (!/^\s+$/) { # not a blank line if ( $found ) { $inheader = 0; # don't accept more =item (unless inlist) } else { @$pod = (); # reset $inheader = 1; # start over next; } } if (/^=over/) { ++$inlist; } elsif (/^=back/) { last if $found && !$inheader && !$inlist; --$inlist; } push @$pod, $_; # ++$found if /^\w/; # found descriptive text } @$pod = () unless $found; if (!@$pod) { CORE::die( "No documentation for perl variable '$opt' found\n" ); } close $fh or $self->die( "Can't close $perlvar: $!" ); return; } #.......................................................................... sub search_perlop { my ($self,$found_things,$pod) = @_; $self->not_dynamic( 1 ); my $perlop = shift @$found_things; # XXX FIXME: getting filehandles should probably be done in a single place # especially since we need to support UTF8 or other encoding when dealing # with perlop, perlfunc, perlapi, perlfaq[1-9] my $fh = $self->open_fh('<', $perlop); my $thing = $self->opt_f; my $previous_line; my $push = 0; my $seen_item = 0; my $skip = 1; while( my $line = <$fh> ) { $line =~ /^=encoding\s+(\S+)/ && $self->set_encoding($fh, $1); # only start search after we hit the operator section if ($line =~ m!^X!) { $skip = 0; } next if $skip; # strategy is to capture the previous line until we get a match on X<$thingy> # if the current line contains X<$thingy>, then we push "=over", the previous line, # the current line and keep pushing current line until we see a ^X, # then we chop off final line from @$pod and add =back # # At that point, Bob's your uncle. if ( $line =~ m!X<+\s*\Q$thing\E\s*>+!) { if ( $previous_line ) { push @$pod, "=over 8\n\n", $previous_line; $previous_line = ""; } push @$pod, $line; $push = 1; } elsif ( $push and $line =~ m!^=item\s*.*$! ) { $seen_item = 1; } elsif ( $push and $seen_item and $line =~ m!^X<+\s*[ a-z,?-]+\s*>+!) { $push = 0; $seen_item = 0; last; } elsif ( $push ) { push @$pod, $line; } else { $previous_line = $line; } } #end while # we overfilled by 1 line, so pop off final array element if we have any if ( scalar @$pod ) { pop @$pod; # and add the =back push @$pod, "\n\n=back\n"; DEBUG > 8 and print "PERLOP POD --->" . (join "", @$pod) . "<---\n"; } else { DEBUG > 4 and print "No pod from perlop\n"; } close $fh; return; } #.......................................................................... sub search_perlapi { my($self, $found_things, $pod) = @_; DEBUG > 2 and print "Search: @$found_things\n"; my $perlapi = shift @$found_things; my $fh = $self->open_fh('<', $perlapi); my $search_re = quotemeta($self->opt_a); DEBUG > 2 and print "Going to perlapi-scan for $search_re in $perlapi\n"; local $_; # Look for our function my $found = 0; my $inlist = 0; my @related; my $related_re; while (<$fh>) { /^=encoding\s+(\S+)/ && $self->set_encoding($fh, $1); if ( m/^=item\s+$search_re\b/ ) { $found = 1; } elsif (@related > 1 and /^=item/) { $related_re ||= join "|", @related; if (m/^=item\s+(?:$related_re)\b/) { $found = 1; } else { last; } } elsif (/^=item/) { last if $found > 1 and not $inlist; } elsif ($found and /^X<[^>]+>/) { push @related, m/X<([^>]+)>/g; } next unless $found; if (/^=over/) { ++$inlist; } elsif (/^=back/) { last if $found > 1 and not $inlist; --$inlist; } push @$pod, $_; ++$found if /^\w/; # found descriptive text } if (!@$pod) { CORE::die( sprintf "No documentation for perl api function '%s' found\n", $self->opt_a ) ; } close $fh or $self->die( "Can't open $perlapi: $!" ); return; } #.......................................................................... sub search_perlfunc { my($self, $found_things, $pod) = @_; DEBUG > 2 and print "Search: @$found_things\n"; my $pfunc = shift @$found_things; my $fh = $self->open_fh("<", $pfunc); # "Funk is its own reward" # Functions like -r, -e, etc. are listed under `-X'. my $search_re = ($self->opt_f =~ /^-[rwxoRWXOeszfdlpSbctugkTBMAC]$/) ? '(?:I<)?-X' : quotemeta($self->opt_f) ; DEBUG > 2 and print "Going to perlfunc-scan for $search_re in $pfunc\n"; my $re = 'Alphabetical Listing of Perl Functions'; # Check available translator or backup to default (english) if ( $self->opt_L && defined $self->{'translators'}->[0] ) { my $tr = $self->{'translators'}->[0]; $re = $tr->search_perlfunc_re if $tr->can('search_perlfunc_re'); if ( $] < 5.008 ) { $self->aside("Your old perl doesn't really have proper unicode support."); } } # Skip introduction local $_; while (<$fh>) { /^=encoding\s+(\S+)/ && $self->set_encoding($fh, $1); last if /^=head2 (?:$re|Alphabetical Listing of Perl Functions)/; } # Look for our function my $found = 0; my $inlist = 0; my @perlops = qw(m q qq qr qx qw s tr y); my @related; my $related_re; while (<$fh>) { # "The Mothership Connection is here!" last if( grep{ $self->opt_f eq $_ }@perlops ); if ( /^=over/ and not $found ) { ++$inlist; } elsif ( /^=back/ and not $found and $inlist ) { --$inlist; } if ( m/^=item\s+$search_re\b/ and $inlist < 2 ) { $found = 1; } elsif (@related > 1 and /^=item/) { $related_re ||= join "|", @related; if (m/^=item\s+(?:$related_re)\b/) { $found = 1; } else { last if $found > 1 and $inlist < 2; } } elsif (/^=item|^=back/) { last if $found > 1 and $inlist < 2; } elsif ($found and /^X<[^>]+>/) { push @related, m/X<([^>]+)>/g; } next unless $found; if (/^=over/) { ++$inlist; } elsif (/^=back/) { --$inlist; } push @$pod, $_; ++$found if /^\w/; # found descriptive text } if( !@$pod ){ $self->search_perlop( $found_things, $pod ); } if (!@$pod) { CORE::die( sprintf "No documentation for perl function '%s' found\n", $self->opt_f ) ; } close $fh or $self->die( "Can't close $pfunc: $!" ); return; } #.......................................................................... sub search_perlfaqs { my( $self, $found_things, $pod) = @_; my $found = 0; my %found_in; my $search_key = $self->opt_q; my $rx = eval { qr/$search_key/ } or $self->die( <die( "invalid file spec: $!" ) if $file =~ /[<>|]/; my $fh = $self->open_fh("<", $file); while (<$fh>) { /^=encoding\s+(\S+)/ && $self->set_encoding($fh, $1); if ( m/^=head2\s+.*(?:$search_key)/i ) { $found = 1; push @$pod, "=head1 Found in $file\n\n" unless $found_in{$file}++; } elsif (/^=head[12]/) { $found = 0; } next unless $found; push @$pod, $_; } close($fh); } CORE::die("No documentation for perl FAQ keyword '$search_key' found\n") unless @$pod; if ( $self->opt_l ) { CORE::die((join "\n", keys %found_in) . "\n"); } return; } #.......................................................................... sub render_findings { # Return the filename to open my($self, $found_things) = @_; my $formatter_class = $self->{'formatter_class'} || $self->die( "No formatter class set!?" ); my $formatter = $formatter_class->can('new') ? $formatter_class->new : $formatter_class ; if(! @$found_things) { $self->die( "Nothing found?!" ); # should have been caught before here } elsif(@$found_things > 1) { $self->warn( "Perldoc is only really meant for reading one document at a time.\n", "So these parameters are being ignored: ", join(' ', @$found_things[1 .. $#$found_things] ), "\n" ); } my $file = $found_things->[0]; DEBUG > 3 and printf "Formatter switches now: [%s]\n", join ' ', map "[@$_]", @{ $self->{'formatter_switches'} }; # Set formatter options: if( ref $formatter ) { foreach my $f (@{ $self->{'formatter_switches'} || [] }) { my($switch, $value, $silent_fail) = @$f; if( $formatter->can($switch) ) { eval { $formatter->$switch( defined($value) ? $value : () ) }; $self->warn( "Got an error when setting $formatter_class\->$switch:\n$@\n" ) if $@; } else { if( $silent_fail or $switch =~ m/^__/s ) { DEBUG > 2 and print "Formatter $formatter_class doesn't support $switch\n"; } else { $self->warn( "$formatter_class doesn't recognize the $switch switch.\n" ); } } } } $self->{'output_is_binary'} = $formatter->can('write_with_binmode') && $formatter->write_with_binmode; if( $self->{podnames} and exists $self->{podnames}{$file} and $formatter->can('name') ) { $formatter->name($self->{podnames}{$file}); } my ($out_fh, $out) = $self->new_output_file( ( $formatter->can('output_extension') && $formatter->output_extension ) || undef, $self->useful_filename_bit, ); # Now, finally, do the formatting! { local $^W = $^W; if(DEBUG() or $self->opt_D) { # feh, let 'em see it } else { $^W = 0; # The average user just has no reason to be seeing # $^W-suppressible warnings from the formatting! } eval { $formatter->parse_from_file( $file, $out_fh ) }; } $self->warn( "Error while formatting with $formatter_class:\n $@\n" ) if $@; DEBUG > 2 and print "Back from formatting with $formatter_class\n"; close $out_fh or $self->warn( "Can't close $out: $!\n(Did $formatter already close it?)" ); sleep 0; sleep 0; sleep 0; # Give the system a few timeslices to meditate on the fact # that the output file does in fact exist and is closed. $self->unlink_if_temp_file($file); unless( -s $out ) { if( $formatter->can( 'if_zero_length' ) ) { # Basically this is just a hook for Pod::Simple::Checker; since # what other class could /happily/ format an input file with Pod # as a 0-length output file? $formatter->if_zero_length( $file, $out, $out_fh ); } else { $self->warn( "Got a 0-length file from $$found_things[0] via $formatter_class!?\n" ); } } DEBUG and print "Finished writing to $out.\n"; return($out, $formatter) if wantarray; return $out; } #.......................................................................... sub unlink_if_temp_file { # Unlink the specified file IFF it's in the list of temp files. # Really only used in the case of -f / -q things when we can # throw away the dynamically generated source pod file once # we've formatted it. # my($self, $file) = @_; return unless defined $file and length $file; my $temp_file_list = $self->{'temp_file_list'} || return; if(grep $_ eq $file, @$temp_file_list) { $self->aside("Unlinking $file\n"); unlink($file) or $self->warn( "Odd, couldn't unlink $file: $!" ); } else { DEBUG > 1 and print "$file isn't a temp file, so not unlinking.\n"; } return; } #.......................................................................... sub after_rendering { my $self = $_[0]; $self->after_rendering_VMS if $self->is_vms; $self->after_rendering_MSWin32 if $self->is_mswin32; $self->after_rendering_Dos if $self->is_dos; $self->after_rendering_OS2 if $self->is_os2; return; } sub after_rendering_VMS { return } sub after_rendering_Dos { return } sub after_rendering_OS2 { return } sub after_rendering_MSWin32 { return } #.......................................................................... # : : : : : : : : : #.......................................................................... sub minus_f_nocase { # i.e., do like -f, but without regard to case my($self, $dir, $file) = @_; my $path = catfile($dir,$file); return $path if -f $path and -r _; if(!$self->opt_i or $self->is_vms or $self->is_mswin32 or $self->is_dos or $self->is_os2 ) { # On a case-forgiving file system, or if case is important, # that is it, all we can do. $self->warn( "Ignored $path: unreadable\n" ) if -f _; return ''; } local *DIR; my @p = ($dir); my($p,$cip); foreach $p (splitdir $file){ my $try = catfile @p, $p; $self->aside("Scrutinizing $try...\n"); stat $try; if (-d _) { push @p, $p; if ( $p eq $self->{'target'} ) { my $tmp_path = catfile @p; my $path_f = 0; for (@{ $self->{'found'} }) { $path_f = 1 if $_ eq $tmp_path; } push (@{ $self->{'found'} }, $tmp_path) unless $path_f; $self->aside( "Found as $tmp_path but directory\n" ); } } elsif (-f _ && -r _ && lc($try) eq lc($path)) { return $try; } elsif (-f _) { $self->warn( "Ignored $try: unreadable or file/dir mismatch\n" ); } elsif (-d catdir(@p)) { # at least we see the containing directory! my $found = 0; my $lcp = lc $p; my $p_dirspec = catdir(@p); opendir DIR, $p_dirspec or $self->die( "opendir $p_dirspec: $!" ); while(defined( $cip = readdir(DIR) )) { if (lc $cip eq $lcp){ $found++; last; # XXX stop at the first? what if there's others? } } closedir DIR or $self->die( "closedir $p_dirspec: $!" ); return "" unless $found; push @p, $cip; my $p_filespec = catfile(@p); return $p_filespec if -f $p_filespec and -r _; $self->warn( "Ignored $p_filespec: unreadable\n" ) if -f _; } } return ""; } #.......................................................................... sub pagers_guessing { # TODO: This whole subroutine needs to be rewritten. It's semi-insane # right now. my $self = shift; my @pagers; push @pagers, $self->pagers; $self->{'pagers'} = \@pagers; if ($self->is_mswin32) { push @pagers, qw( more< less notepad ); unshift @pagers, $ENV{PAGER} if $ENV{PAGER}; } elsif ($self->is_vms) { push @pagers, qw( most more less type/page ); } elsif ($self->is_dos) { push @pagers, qw( less.exe more.com< ); unshift @pagers, $ENV{PAGER} if $ENV{PAGER}; } elsif ( $self->is_amigaos) { push @pagers, qw( /SYS/Utilities/MultiView /SYS/Utilities/More /C/TYPE ); unshift @pagers, "$ENV{PAGER}" if $ENV{PAGER}; } else { if ($self->is_os2) { unshift @pagers, 'less', 'cmd /c more <'; } push @pagers, qw( more less pg view cat ); unshift @pagers, "$ENV{PAGER} <" if $ENV{PAGER}; } if ($self->is_cygwin) { if (($pagers[0] eq 'less') || ($pagers[0] eq '/usr/bin/less')) { unshift @pagers, '/usr/bin/less -isrR'; unshift @pagers, $ENV{PAGER} if $ENV{PAGER}; } } if ( $self->opt_m ) { unshift @pagers, "$ENV{PERLDOC_SRC_PAGER}" if $ENV{PERLDOC_SRC_PAGER} } else { unshift @pagers, "$ENV{MANPAGER} <" if $ENV{MANPAGER}; unshift @pagers, "$ENV{PERLDOC_PAGER} <" if $ENV{PERLDOC_PAGER}; } $self->aside("Pagers: ", (join ", ", @pagers)); return; } #.......................................................................... sub page_module_file { my($self, @found) = @_; # Security note: # Don't ever just pass this off to anything like MSWin's "start.exe", # since we might be calling on a .pl file, and we wouldn't want that # to actually /execute/ the file that we just want to page thru! # Also a consideration if one were to use a web browser as a pager; # doing so could trigger the browser's MIME mapping for whatever # it thinks .pm/.pl/whatever is. Probably just a (useless and # annoying) "Save as..." dialog, but potentially executing the file # in question -- particularly in the case of MSIE and it's, ahem, # occasionally hazy distinction between OS-local extension # associations, and browser-specific MIME mappings. if(@found > 1) { $self->warn( "Perldoc is only really meant for reading one document at a time.\n" . "So these files are being ignored: " . join(' ', @found[1 .. $#found] ) . "\n" ) } return $self->page($found[0], $self->{'output_to_stdout'}, $self->pagers); } #.......................................................................... sub check_file { my($self, $dir, $file) = @_; unless( ref $self ) { # Should never get called: $Carp::Verbose = 1; require Carp; Carp::croak( join '', "Crazy ", __PACKAGE__, " error:\n", "check_file must be an object_method!\n", "Aborting" ); } if(length $dir and not -d $dir) { DEBUG > 3 and print " No dir $dir -- skipping.\n"; return ""; } my $path = $self->minus_f_nocase($dir,$file); if( length $path and ($self->opt_m ? $self->isprintable($path) : $self->containspod($path)) ) { DEBUG > 3 and print " The file $path indeed looks promising!\n"; return $path; } DEBUG > 3 and print " No good: $file in $dir\n"; return ""; } sub isprintable { my($self, $file, $readit) = @_; my $size= 1024; my $maxunprintfrac= 0.2; # tolerate some unprintables for UTF-8 comments etc. return 1 if !$readit && $file =~ /\.(?:pl|pm|pod|cmd|com|bat)\z/i; my $data; local($_); my $fh = $self->open_fh("<", $file); read $fh, $data, $size; close $fh; $size= length($data); $data =~ tr/\x09-\x0D\x20-\x7E//d; return length($data) <= $size*$maxunprintfrac; } #.......................................................................... sub containspod { my($self, $file, $readit) = @_; return 1 if !$readit && $file =~ /\.pod\z/i; # Under cygwin the /usr/bin/perl is legal executable, but # you cannot open a file with that name. It must be spelled # out as "/usr/bin/perl.exe". # # The following if-case under cygwin prevents error # # $ perldoc perl # Cannot open /usr/bin/perl: no such file or directory # # This would work though # # $ perldoc perl.pod if ( $self->is_cygwin and -x $file and -f "$file.exe" ) { $self->warn( "Cygwin $file.exe search skipped\n" ) if DEBUG or $self->opt_D; return 0; } local($_); my $fh = $self->open_fh("<", $file); while (<$fh>) { if (/^=head/) { close($fh) or $self->die( "Can't close $file: $!" ); return 1; } } close($fh) or $self->die( "Can't close $file: $!" ); return 0; } #.......................................................................... sub maybe_extend_searchpath { my $self = shift; # Does this look like a module or extension directory? if (-f "Makefile.PL" || -f "Build.PL") { push @{$self->{search_path} }, '.','lib'; # don't add if superuser if ($< && $> && -d "blib") { # don't be looking too hard now! push @{ $self->{search_path} }, 'blib'; $self->warn( $@ ) if $@ && $self->opt_D; } } return; } #.......................................................................... sub new_output_file { my $self = shift; my $outspec = $self->opt_d; # Yes, -d overrides all else! # So don't call this twice per format-job! return $self->new_tempfile(@_) unless defined $outspec and length $outspec; # Otherwise open a write-handle on opt_d!f DEBUG > 3 and print "About to try writing to specified output file $outspec\n"; my $fh = $self->open_fh(">", $outspec); DEBUG > 3 and print "Successfully opened $outspec\n"; binmode($fh) if $self->{'output_is_binary'}; return($fh, $outspec); } #.......................................................................... sub useful_filename_bit { # This tries to provide a meaningful bit of text to do with the query, # such as can be used in naming the file -- since if we're going to be # opening windows on temp files (as a "pager" may well do!) then it's # better if the temp file's name (which may well be used as the window # title) isn't ALL just random garbage! # In other words "perldoc_LWPSimple_2371981429" is a better temp file # name than "perldoc_2371981429". So this routine is what tries to # provide the "LWPSimple" bit. # my $self = shift; my $pages = $self->{'pages'} || return undef; return undef unless @$pages; my $chunk = $pages->[0]; return undef unless defined $chunk; $chunk =~ s/:://g; $chunk =~ s/\.\w+$//g; # strip any extension if( $chunk =~ m/([^\#\\:\/\$]+)$/s ) { # get basename, if it's a file $chunk = $1; } else { return undef; } $chunk =~ s/[^a-zA-Z0-9]+//g; # leave ONLY a-zA-Z0-9 things! $chunk = substr($chunk, -10) if length($chunk) > 10; return $chunk; } #.......................................................................... sub new_tempfile { # $self->new_tempfile( [$suffix, [$infix] ] ) my $self = shift; ++$Temp_Files_Created; require File::Temp; return File::Temp::tempfile(UNLINK => 1); } #.......................................................................... sub page { # apply a pager to the output file my ($self, $output, $output_to_stdout, @pagers) = @_; if ($output_to_stdout) { $self->aside("Sending unpaged output to STDOUT.\n"); my $fh = $self->open_fh("<", $output); local $_; while (<$fh>) { print or $self->die( "Can't print to stdout: $!" ); } close $fh or $self->die( "Can't close while $output: $!" ); $self->unlink_if_temp_file($output); } else { # On VMS, quoting prevents logical expansion, and temp files with no # extension get the wrong default extension (such as .LIS for TYPE) $output = VMS::Filespec::rmsexpand($output, '.') if $self->is_vms; $output =~ s{/}{\\}g if $self->is_mswin32 || $self->is_dos; # Altho "/" under MSWin is in theory good as a pathsep, # many many corners of the OS don't like it. So we # have to force it to be "\" to make everyone happy. # if we are on an amiga convert unix path to an amiga one $output =~ s/^\/(.*)\/(.*)/$1:$2/ if $self->is_amigaos; foreach my $pager (@pagers) { $self->aside("About to try calling $pager $output\n"); if ($self->is_vms) { last if system("$pager $output") == 0; } elsif($self->is_amigaos) { last if system($pager, $output) == 0; } else { last if system("$pager \"$output\"") == 0; } } } return; } #.......................................................................... sub searchfor { my($self, $recurse,$s,@dirs) = @_; $s =~ s!::!/!g; $s = VMS::Filespec::unixify($s) if $self->is_vms; return $s if -f $s && $self->containspod($s); $self->aside( "Looking for $s in @dirs\n" ); my $ret; my $i; my $dir; $self->{'target'} = (splitdir $s)[-1]; # XXX: why not use File::Basename? for ($i=0; $i<@dirs; $i++) { $dir = $dirs[$i]; next unless -d $dir; ($dir = VMS::Filespec::unixpath($dir)) =~ s!/\z!! if $self->is_vms; if ( (! $self->opt_m && ( $ret = $self->check_file($dir,"$s.pod"))) or ( $ret = $self->check_file($dir,"$s.pm")) or ( $ret = $self->check_file($dir,$s)) or ( $self->is_vms and $ret = $self->check_file($dir,"$s.com")) or ( $self->is_os2 and $ret = $self->check_file($dir,"$s.cmd")) or ( ($self->is_mswin32 or $self->is_dos or $self->is_os2) and $ret = $self->check_file($dir,"$s.bat")) or ( $ret = $self->check_file("$dir/pod","$s.pod")) or ( $ret = $self->check_file("$dir/pod",$s)) or ( $ret = $self->check_file("$dir/pods","$s.pod")) or ( $ret = $self->check_file("$dir/pods",$s)) ) { DEBUG > 1 and print " Found $ret\n"; return $ret; } if ($recurse) { opendir(D,$dir) or $self->die( "Can't opendir $dir: $!" ); my @newdirs = map catfile($dir, $_), grep { not /^\.\.?\z/s and not /^auto\z/s and # save time! don't search auto dirs -d catfile($dir, $_) } readdir D; closedir(D) or $self->die( "Can't closedir $dir: $!" ); next unless @newdirs; # what a wicked map! @newdirs = map((s/\.dir\z//,$_)[1],@newdirs) if $self->is_vms; $self->aside( "Also looking in @newdirs\n" ); push(@dirs,@newdirs); } } return (); } #.......................................................................... { my $already_asserted; sub assert_closing_stdout { my $self = shift; return if $already_asserted; eval q~ END { close(STDOUT) || CORE::die "Can't close STDOUT: $!" } ~; # What for? to let the pager know that nothing more will come? $self->die( $@ ) if $@; $already_asserted = 1; return; } } #.......................................................................... sub tweak_found_pathnames { my($self, $found) = @_; if ($self->is_mswin32) { foreach (@$found) { s,/,\\,g } } foreach (@$found) { s,',\\',g } # RT 37347 return; } #.......................................................................... # : : : : : : : : : #.......................................................................... sub am_taint_checking { my $self = shift; $self->die( "NO ENVIRONMENT?!?!" ) unless keys %ENV; # reset iterator along the way my($k,$v) = each %ENV; return is_tainted($v); } #.......................................................................... sub is_tainted { # just a function my $arg = shift; my $nada = substr($arg, 0, 0); # zero-length! local $@; # preserve the caller's version of $@ eval { eval "# $nada" }; return length($@) != 0; } #.......................................................................... sub drop_privs_maybe { my $self = shift; DEBUG and print "Attempting to drop privs...\n"; # Attempt to drop privs if we should be tainting and aren't if (!( $self->is_vms || $self->is_mswin32 || $self->is_dos || $self->is_os2 ) && ($> == 0 || $< == 0) && !$self->am_taint_checking() ) { my $id = eval { getpwnam("nobody") }; $id = eval { getpwnam("nouser") } unless defined $id; $id = -2 unless defined $id; # # According to Stevens' APUE and various # (BSD, Solaris, HP-UX) man pages, setting # the real uid first and effective uid second # is the way to go if one wants to drop privileges, # because if one changes into an effective uid of # non-zero, one cannot change the real uid any more. # # Actually, it gets even messier. There is # a third uid, called the saved uid, and as # long as that is zero, one can get back to # uid of zero. Setting the real-effective *twice* # helps in *most* systems (FreeBSD and Solaris) # but apparently in HP-UX even this doesn't help: # the saved uid stays zero (apparently the only way # in HP-UX to change saved uid is to call setuid() # when the effective uid is zero). # eval { $< = $id; # real uid $> = $id; # effective uid $< = $id; # real uid $> = $id; # effective uid }; if( !$@ && $< && $> ) { DEBUG and print "OK, I dropped privileges.\n"; } elsif( $self->opt_U ) { DEBUG and print "Couldn't drop privileges, but in -U mode, so feh." } else { DEBUG and print "Hm, couldn't drop privileges. Ah well.\n"; # We used to die here; but that seemed pointless. } } return; } #.......................................................................... 1; __END__ # Pod::Text::Color -- Convert POD data to formatted color ASCII text # # This is just a basic proof of concept. It should later be modified to make # better use of color, take options changing what colors are used for what # text, and the like. # # Copyright 1999, 2001, 2004, 2006, 2008, 2009, 2014 # Russ Allbery # # This program is free software; you may redistribute it and/or modify it # under the same terms as Perl itself. ############################################################################## # Modules and declarations ############################################################################## package Pod::Text::Color; use 5.006; use strict; use warnings; use Pod::Text (); use Term::ANSIColor qw(colored); use vars qw(@ISA $VERSION); @ISA = qw(Pod::Text); $VERSION = '4.10'; ############################################################################## # Overrides ############################################################################## # Make level one headings bold. sub cmd_head1 { my ($self, $attrs, $text) = @_; $text =~ s/\s+$//; $self->SUPER::cmd_head1 ($attrs, colored ($text, 'bold')); } # Make level two headings bold. sub cmd_head2 { my ($self, $attrs, $text) = @_; $text =~ s/\s+$//; $self->SUPER::cmd_head2 ($attrs, colored ($text, 'bold')); } # Fix the various formatting codes. sub cmd_b { return colored ($_[2], 'bold') } sub cmd_f { return colored ($_[2], 'cyan') } sub cmd_i { return colored ($_[2], 'yellow') } # Output any included code in green. sub output_code { my ($self, $code) = @_; $code = colored ($code, 'green'); $self->output ($code); } # Strip all of the formatting from a provided string, returning the stripped # version. We will eventually want to use colorstrip() from Term::ANSIColor, # but it's fairly new so avoid the tight dependency. sub strip_format { my ($self, $text) = @_; $text =~ s/\e\[[\d;]*m//g; return $text; } # We unfortunately have to override the wrapping code here, since the normal # wrapping code gets really confused by all the escape sequences. sub wrap { my $self = shift; local $_ = shift; my $output = ''; my $spaces = ' ' x $$self{MARGIN}; my $width = $$self{opt_width} - $$self{MARGIN}; # We have to do $shortchar and $longchar in variables because the # construct ${char}{0,$width} didn't do the right thing until Perl 5.8.x. my $char = '(?:(?:\e\[[\d;]+m)*[^\n])'; my $shortchar = $char . "{0,$width}"; my $longchar = $char . "{$width}"; while (length > $width) { if (s/^($shortchar)\s+// || s/^($longchar)//) { $output .= $spaces . $1 . "\n"; } else { last; } } $output .= $spaces . $_; $output =~ s/\s+$/\n\n/; $output; } ############################################################################## # Module return value and documentation ############################################################################## 1; __END__ # Pod::Text::Overstrike -- Convert POD data to formatted overstrike text # # This was written because the output from: # # pod2text Text.pm > plain.txt; less plain.txt # # is not as rich as the output from # # pod2man Text.pm | nroff -man > fancy.txt; less fancy.txt # # and because both Pod::Text::Color and Pod::Text::Termcap are not device # independent. # # Created by Joe Smith 30-Nov-2000 # (based on Pod::Text::Color by Russ Allbery ) # Copyright 2000 Joe Smith . # Copyright 2001, 2004, 2008, 2014 Russ Allbery . # # This program is free software; you may redistribute it and/or modify it # under the same terms as Perl itself. ############################################################################## # Modules and declarations ############################################################################## package Pod::Text::Overstrike; use 5.006; use strict; use warnings; use vars qw(@ISA $VERSION); use Pod::Text (); @ISA = qw(Pod::Text); $VERSION = '4.10'; ############################################################################## # Overrides ############################################################################## # Make level one headings bold, overriding any existing formatting. sub cmd_head1 { my ($self, $attrs, $text) = @_; $text =~ s/\s+$//; $text = $self->strip_format ($text); $text =~ s/(.)/$1\b$1/g; return $self->SUPER::cmd_head1 ($attrs, $text); } # Make level two headings bold, overriding any existing formatting. sub cmd_head2 { my ($self, $attrs, $text) = @_; $text =~ s/\s+$//; $text = $self->strip_format ($text); $text =~ s/(.)/$1\b$1/g; return $self->SUPER::cmd_head2 ($attrs, $text); } # Make level three headings underscored, overriding any existing formatting. sub cmd_head3 { my ($self, $attrs, $text) = @_; $text =~ s/\s+$//; $text = $self->strip_format ($text); $text =~ s/(.)/_\b$1/g; return $self->SUPER::cmd_head3 ($attrs, $text); } # Level four headings look like level three headings. sub cmd_head4 { my ($self, $attrs, $text) = @_; $text =~ s/\s+$//; $text = $self->strip_format ($text); $text =~ s/(.)/_\b$1/g; return $self->SUPER::cmd_head4 ($attrs, $text); } # The common code for handling all headers. We have to override to avoid # interpolating twice and because we don't want to honor alt. sub heading { my ($self, $text, $indent, $marker) = @_; $self->item ("\n\n") if defined $$self{ITEM}; $text .= "\n" if $$self{opt_loose}; my $margin = ' ' x ($$self{opt_margin} + $indent); $self->output ($margin . $text . "\n"); return ''; } # Fix the various formatting codes. sub cmd_b { local $_ = $_[0]->strip_format ($_[2]); s/(.)/$1\b$1/g; $_ } sub cmd_f { local $_ = $_[0]->strip_format ($_[2]); s/(.)/_\b$1/g; $_ } sub cmd_i { local $_ = $_[0]->strip_format ($_[2]); s/(.)/_\b$1/g; $_ } # Output any included code in bold. sub output_code { my ($self, $code) = @_; $code =~ s/(.)/$1\b$1/g; $self->output ($code); } # Strip all of the formatting from a provided string, returning the stripped # version. sub strip_format { my ($self, $text) = @_; $text =~ s/(.)[\b]\1/$1/g; $text =~ s/_[\b]//g; return $text; } # We unfortunately have to override the wrapping code here, since the normal # wrapping code gets really confused by all the backspaces. sub wrap { my $self = shift; local $_ = shift; my $output = ''; my $spaces = ' ' x $$self{MARGIN}; my $width = $$self{opt_width} - $$self{MARGIN}; while (length > $width) { # This regex represents a single character, that's possibly underlined # or in bold (in which case, it's three characters; the character, a # backspace, and a character). Use [^\n] rather than . to protect # against odd settings of $*. my $char = '(?:[^\n][\b])?[^\n]'; if (s/^((?>$char){0,$width})(?:\Z|\s+)//) { $output .= $spaces . $1 . "\n"; } else { last; } } $output .= $spaces . $_; $output =~ s/\s+$/\n\n/; return $output; } ############################################################################## # Module return value and documentation ############################################################################## 1; __END__ # Pod::Text::Termcap -- Convert POD data to ASCII text with format escapes. # # This is a simple subclass of Pod::Text that overrides a few key methods to # output the right termcap escape sequences for formatted text on the current # terminal type. # # Copyright 1999, 2001, 2002, 2004, 2006, 2008, 2009, 2014, 2015 # Russ Allbery # # This program is free software; you may redistribute it and/or modify it # under the same terms as Perl itself. ############################################################################## # Modules and declarations ############################################################################## package Pod::Text::Termcap; use 5.006; use strict; use warnings; use Pod::Text (); use POSIX (); use Term::Cap; use vars qw(@ISA $VERSION); @ISA = qw(Pod::Text); $VERSION = '4.10'; ############################################################################## # Overrides ############################################################################## # In the initialization method, grab our terminal characteristics as well as # do all the stuff we normally do. sub new { my ($self, @args) = @_; my ($ospeed, $term, $termios); $self = $self->SUPER::new (@args); # $ENV{HOME} is usually not set on Windows. The default Term::Cap path # may not work on Solaris. unless (exists $ENV{TERMPATH}) { my $home = exists $ENV{HOME} ? "$ENV{HOME}/.termcap:" : ''; $ENV{TERMPATH} = "${home}/etc/termcap:/usr/share/misc/termcap:/usr/share/lib/termcap"; } # Fall back on a hard-coded terminal speed if POSIX::Termios isn't # available (such as on VMS). eval { $termios = POSIX::Termios->new }; if ($@) { $ospeed = 9600; } else { $termios->getattr; $ospeed = $termios->getospeed || 9600; } # Fall back on the ANSI escape sequences if Term::Cap doesn't work. eval { $term = Tgetent Term::Cap { TERM => undef, OSPEED => $ospeed } }; $$self{BOLD} = $$term{_md} || "\e[1m"; $$self{UNDL} = $$term{_us} || "\e[4m"; $$self{NORM} = $$term{_me} || "\e[m"; unless (defined $$self{width}) { $$self{opt_width} = $ENV{COLUMNS} || $$term{_co} || 80; $$self{opt_width} -= 2; } return $self; } # Make level one headings bold. sub cmd_head1 { my ($self, $attrs, $text) = @_; $text =~ s/\s+$//; $self->SUPER::cmd_head1 ($attrs, "$$self{BOLD}$text$$self{NORM}"); } # Make level two headings bold. sub cmd_head2 { my ($self, $attrs, $text) = @_; $text =~ s/\s+$//; $self->SUPER::cmd_head2 ($attrs, "$$self{BOLD}$text$$self{NORM}"); } # Fix up B<> and I<>. Note that we intentionally don't do F<>. sub cmd_b { my $self = shift; return "$$self{BOLD}$_[1]$$self{NORM}" } sub cmd_i { my $self = shift; return "$$self{UNDL}$_[1]$$self{NORM}" } # Output any included code in bold. sub output_code { my ($self, $code) = @_; $self->output ($$self{BOLD} . $code . $$self{NORM}); } # Strip all of the formatting from a provided string, returning the stripped # version. sub strip_format { my ($self, $text) = @_; $text =~ s/\Q$$self{BOLD}//g; $text =~ s/\Q$$self{UNDL}//g; $text =~ s/\Q$$self{NORM}//g; return $text; } # Override the wrapping code to ignore the special sequences. sub wrap { my $self = shift; local $_ = shift; my $output = ''; my $spaces = ' ' x $$self{MARGIN}; my $width = $$self{opt_width} - $$self{MARGIN}; # $codes matches a single special sequence. $char matches any number of # special sequences preceding a single character other than a newline. # We have to do $shortchar and $longchar in variables because the # construct ${char}{0,$width} didn't do the right thing until Perl 5.8.x. my $codes = "(?:\Q$$self{BOLD}\E|\Q$$self{UNDL}\E|\Q$$self{NORM}\E)"; my $char = "(?:$codes*[^\\n])"; my $shortchar = $char . "{0,$width}"; my $longchar = $char . "{$width}"; while (length > $width) { if (s/^($shortchar)\s+// || s/^($longchar)//) { $output .= $spaces . $1 . "\n"; } else { last; } } $output .= $spaces . $_; $output =~ s/\s+$/\n\n/; return $output; } ############################################################################## # Module return value and documentation ############################################################################## 1; __END__ require 5.005; package Pod::Simple::Search; use strict; use vars qw($VERSION $MAX_VERSION_WITHIN $SLEEPY); $VERSION = '3.35'; ## Current version of this package BEGIN { *DEBUG = sub () {0} unless defined &DEBUG; } # set DEBUG level use Carp (); $SLEEPY = 1 if !defined $SLEEPY and $^O =~ /mswin|mac/i; # flag to occasionally sleep for $SLEEPY - 1 seconds. $MAX_VERSION_WITHIN ||= 60; my $IS_CASE_INSENSITIVE = -e uc __FILE__ && -e lc __FILE__; ############################################################################# #use diagnostics; use File::Spec (); use File::Basename qw( basename dirname ); use Config (); use Cwd qw( cwd ); #========================================================================== __PACKAGE__->_accessorize( # Make my dumb accessor methods 'callback', 'progress', 'dir_prefix', 'inc', 'laborious', 'limit_glob', 'limit_re', 'shadows', 'verbose', 'name2path', 'path2name', 'recurse', 'ciseen' ); #========================================================================== sub new { my $class = shift; my $self = bless {}, ref($class) || $class; $self->init; return $self; } sub init { my $self = shift; $self->inc(1); $self->recurse(1); $self->verbose(DEBUG); return $self; } #-------------------------------------------------------------------------- sub survey { my($self, @search_dirs) = @_; $self = $self->new unless ref $self; # tolerate being a class method $self->_expand_inc( \@search_dirs ); $self->{'_scan_count'} = 0; $self->{'_dirs_visited'} = {}; $self->path2name( {} ); $self->name2path( {} ); $self->ciseen( {} ); $self->limit_re( $self->_limit_glob_to_limit_re ) if $self->{'limit_glob'}; my $cwd = cwd(); my $verbose = $self->verbose; local $_; # don't clobber the caller's $_ ! foreach my $try (@search_dirs) { unless( File::Spec->file_name_is_absolute($try) ) { # make path absolute $try = File::Spec->catfile( $cwd ,$try); } # simplify path $try = File::Spec->canonpath($try); my $start_in; my $modname_prefix; if($self->{'dir_prefix'}) { $start_in = File::Spec->catdir( $try, grep length($_), split '[\\/:]+', $self->{'dir_prefix'} ); $modname_prefix = [grep length($_), split m{[:/\\]}, $self->{'dir_prefix'}]; $verbose and print "Appending \"$self->{'dir_prefix'}\" to $try, ", "giving $start_in (= @$modname_prefix)\n"; } else { $start_in = $try; } if( $self->{'_dirs_visited'}{$start_in} ) { $verbose and print "Directory '$start_in' already seen, skipping.\n"; next; } else { $self->{'_dirs_visited'}{$start_in} = 1; } unless(-e $start_in) { $verbose and print "Skipping non-existent $start_in\n"; next; } my $closure = $self->_make_search_callback; if(-d $start_in) { # Normal case: $verbose and print "Beginning excursion under $start_in\n"; $self->_recurse_dir( $start_in, $closure, $modname_prefix ); $verbose and print "Back from excursion under $start_in\n\n"; } elsif(-f _) { # A excursion consisting of just one file! $_ = basename($start_in); $verbose and print "Pondering $start_in ($_)\n"; $closure->($start_in, $_, 0, []); } else { $verbose and print "Skipping mysterious $start_in\n"; } } $self->progress and $self->progress->done( "Noted $$self{'_scan_count'} Pod files total"); $self->ciseen( {} ); return unless defined wantarray; # void return $self->name2path unless wantarray; # scalar return $self->name2path, $self->path2name; # list } #========================================================================== sub _make_search_callback { my $self = $_[0]; # Put the options in variables, for easy access my( $laborious, $verbose, $shadows, $limit_re, $callback, $progress, $path2name, $name2path, $recurse, $ciseen) = map scalar($self->$_()), qw(laborious verbose shadows limit_re callback progress path2name name2path recurse ciseen); my ($seen, $remember, $files_for); if ($IS_CASE_INSENSITIVE) { $seen = sub { $ciseen->{ lc $_[0] } }; $remember = sub { $name2path->{ $_[0] } = $ciseen->{ lc $_[0] } = $_[1]; }; $files_for = sub { my $n = lc $_[0]; grep { lc $path2name->{$_} eq $n } %{ $path2name } }; } else { $seen = sub { $name2path->{ $_[0] } }; $remember = sub { $name2path->{ $_[0] } = $_[1] }; $files_for = sub { my $n = $_[0]; grep { $path2name->{$_} eq $n } %{ $path2name } }; } my($file, $shortname, $isdir, $modname_bits); return sub { ($file, $shortname, $isdir, $modname_bits) = @_; if($isdir) { # this never gets called on the startdir itself, just subdirs unless( $recurse ) { $verbose and print "Not recursing into '$file' as per requested.\n"; return 'PRUNE'; } if( $self->{'_dirs_visited'}{$file} ) { $verbose and print "Directory '$file' already seen, skipping.\n"; return 'PRUNE'; } print "Looking in dir $file\n" if $verbose; unless ($laborious) { # $laborious overrides pruning if( m/^(\d+\.[\d_]{3,})\z/s and do { my $x = $1; $x =~ tr/_//d; $x != $] } ) { $verbose and print "Perl $] version mismatch on $_, skipping.\n"; return 'PRUNE'; } if( m/^([A-Za-z][a-zA-Z0-9_]*)\z/s ) { $verbose and print "$_ is a well-named module subdir. Looking....\n"; } else { $verbose and print "$_ is a fishy directory name. Skipping.\n"; return 'PRUNE'; } } # end unless $laborious $self->{'_dirs_visited'}{$file} = 1; return; # (not pruning); } # Make sure it's a file even worth even considering if($laborious) { unless( m/\.(pod|pm|plx?)\z/i || -x _ and -T _ # Note that the cheapest operation (the RE) is run first. ) { $verbose > 1 and print " Brushing off uninteresting $file\n"; return; } } else { unless( m/^[-_a-zA-Z0-9]+\.(?:pod|pm|plx?)\z/is ) { $verbose > 1 and print " Brushing off oddly-named $file\n"; return; } } $verbose and print "Considering item $file\n"; my $name = $self->_path2modname( $file, $shortname, $modname_bits ); $verbose > 0.01 and print " Nominating $file as $name\n"; if($limit_re and $name !~ m/$limit_re/i) { $verbose and print "Shunning $name as not matching $limit_re\n"; return; } if( !$shadows and $seen->($name) ) { $verbose and print "Not worth considering $file ", "-- already saw $name as ", join(' ', $files_for->($name)), "\n"; return; } # Put off until as late as possible the expense of # actually reading the file: $progress and $progress->reach($self->{'_scan_count'}, "Scanning $file"); return unless $self->contains_pod( $file ); ++ $self->{'_scan_count'}; # Or finally take note of it: if ( my $prev = $seen->($name) ) { $verbose and print "Duplicate POD found (shadowing?): $name ($file)\n", " Already seen in ", join(' ', $files_for->($name)), "\n"; } else { $remember->($name, $file); # Noting just the first occurrence } $verbose and print " Noting $name = $file\n"; if( $callback ) { local $_ = $_; # insulate from changes, just in case $callback->($file, $name); } $path2name->{$file} = $name; return; } } #========================================================================== sub _path2modname { my($self, $file, $shortname, $modname_bits) = @_; # this code simplifies the POD name for Perl modules: # * remove "site_perl" # * remove e.g. "i586-linux" (from 'archname') # * remove e.g. 5.00503 # * remove pod/ if followed by perl*.pod (e.g. in pod/perlfunc.pod) # * dig into the file for case-preserved name if not already mixed case my @m = @$modname_bits; my $x; my $verbose = $self->verbose; # Shaving off leading naughty-bits while(@m and defined($x = lc( $m[0] )) and( $x eq 'site_perl' or($x eq 'pod' and @m == 1 and $shortname =~ m{^perl.*\.pod$}s ) or $x =~ m{\\d+\\.z\\d+([_.]?\\d+)?} # if looks like a vernum or $x eq lc( $Config::Config{'archname'} ) )) { shift @m } my $name = join '::', @m, $shortname; $self->_simplify_base($name); # On VMS, case-preserved document names can't be constructed from # filenames, so try to extract them from the "=head1 NAME" tag in the # file instead. if ($^O eq 'VMS' && ($name eq lc($name) || $name eq uc($name))) { open PODFILE, "<$file" or die "_path2modname: Can't open $file: $!"; my $in_pod = 0; my $in_name = 0; my $line; while ($line = ) { chomp $line; $in_pod = 1 if ($line =~ m/^=\w/); $in_pod = 0 if ($line =~ m/^=cut/); next unless $in_pod; # skip non-pod text next if ($line =~ m/^\s*\z/); # and blank lines next if ($in_pod && ($line =~ m/^X{'fs_recursion_maxdepth'} || 10; my $verbose = $self->verbose; my $here_string = File::Spec->curdir; my $up_string = File::Spec->updir; $modname_bits ||= []; my $recursor; $recursor = sub { my($dir_long, $dir_bare) = @_; if( @$modname_bits >= 10 ) { $verbose and print "Too deep! [@$modname_bits]\n"; return; } unless(-d $dir_long) { $verbose > 2 and print "But it's not a dir! $dir_long\n"; return; } unless( opendir(INDIR, $dir_long) ) { $verbose > 2 and print "Can't opendir $dir_long : $!\n"; closedir(INDIR); return } # Load all items; put no extension before .pod before .pm before .plx?. my @items = map { $_->[0] } sort { $a->[1] cmp $b->[1] || $b->[2] cmp $a->[2] } map { (my $t = $_) =~ s/[.]p(m|lx?|od)\z//; [$_, $t, lc($1 || 'z') ] } readdir(INDIR); closedir(INDIR); push @$modname_bits, $dir_bare unless $dir_bare eq ''; my $i_full; foreach my $i (@items) { next if $i eq $here_string or $i eq $up_string or $i eq ''; $i_full = File::Spec->catfile( $dir_long, $i ); if(!-r $i_full) { $verbose and print "Skipping unreadable $i_full\n"; } elsif(-f $i_full) { $_ = $i; $callback->( $i_full, $i, 0, $modname_bits ); } elsif(-d _) { $i =~ s/\.DIR\z//i if $^O eq 'VMS'; $_ = $i; my $rv = $callback->( $i_full, $i, 1, $modname_bits ) || ''; if($rv eq 'PRUNE') { $verbose > 1 and print "OK, pruning"; } else { # Otherwise, recurse into it $recursor->( File::Spec->catdir($dir_long, $i) , $i); } } else { $verbose > 1 and print "Skipping oddity $i_full\n"; } } pop @$modname_bits; return; };; local $_; $recursor->($startdir, ''); undef $recursor; # allow it to be GC'd return; } #========================================================================== sub run { # A function, useful in one-liners my $self = __PACKAGE__->new; $self->limit_glob($ARGV[0]) if @ARGV; $self->callback( sub { my($file, $name) = @_; my $version = ''; # Yes, I know we won't catch the version in like a File/Thing.pm # if we see File/Thing.pod first. That's just the way the # cookie crumbles. -- SMB if($file =~ m/\.pod$/i) { # Don't bother looking for $VERSION in .pod files DEBUG and print "Not looking for \$VERSION in .pod $file\n"; } elsif( !open(INPOD, $file) ) { DEBUG and print "Couldn't open $file: $!\n"; close(INPOD); } else { # Sane case: file is readable my $lines = 0; while() { last if $lines++ > $MAX_VERSION_WITHIN; # some degree of sanity if( s/^\s*\$VERSION\s*=\s*//s and m/\d/ ) { DEBUG and print "Found version line (#$lines): $_"; s/\s*\#.*//s; s/\;\s*$//s; s/\s+$//s; s/\t+/ /s; # nix tabs # Optimize the most common cases: $_ = "v$1" if m{^v?["']?([0-9_]+(\.[0-9_]+)*)["']?$}s # like in $VERSION = "3.14159"; or m{\$Revision:\s*([0-9_]+(?:\.[0-9_]+)*)\s*\$}s # like in sprintf("%d.%02d", q$Revision: 4.13 $ =~ /(\d+)\.(\d+)/); ; # Like in sprintf("%d.%s", map {s/_//g; $_} q$Name: release-0_55-public $ =~ /-(\d+)_([\d_]+)/) $_ = sprintf("v%d.%s", map {s/_//g; $_} $1 =~ m/-(\d+)_([\d_]+)/) # snare just the numeric part if m{\$Name:\s*([^\$]+)\$}s ; $version = $_; DEBUG and print "Noting $version as version\n"; last; } } close(INPOD); } print "$name\t$version\t$file\n"; return; # End of callback! }); $self->survey; } #========================================================================== sub simplify_name { my($self, $str) = @_; # Remove all path components # XXX Why not just use basename()? -- SMB if ($^O eq 'MacOS') { $str =~ s{^.*:+}{}s } else { $str =~ s{^.*/+}{}s } $self->_simplify_base($str); return $str; } #========================================================================== sub _simplify_base { # Internal method only # strip Perl's own extensions $_[1] =~ s/\.(pod|pm|plx?)\z//i; # strip meaningless extensions on Win32 and OS/2 $_[1] =~ s/\.(bat|exe|cmd)\z//i if $^O =~ /mswin|os2/i; # strip meaningless extensions on VMS $_[1] =~ s/\.(com)\z//i if $^O eq 'VMS'; return; } #========================================================================== sub _expand_inc { my($self, $search_dirs) = @_; return unless $self->{'inc'}; my %seen = map { File::Spec->rel2abs($_) => 1 } @{ $search_dirs }; if ($^O eq 'MacOS') { push @$search_dirs, grep { !$seen{ File::Spec->rel2abs($_) }++ } $self->_mac_whammy(@INC); # Any other OSs need custom handling here? } else { push @$search_dirs, grep { !$seen{ File::Spec->rel2abs($_) }++ } @INC; } $self->{'laborious'} = 0; # Since inc said to use INC return; } #========================================================================== sub _mac_whammy { # Tolerate '.', './some_dir' and '(../)+some_dir' on Mac OS my @them; (undef,@them) = @_; for $_ (@them) { if ( $_ eq '.' ) { $_ = ':'; } elsif ( $_ =~ s|^((?:\.\./)+)|':' x (length($1)/3)|e ) { $_ = ':'. $_; } else { $_ =~ s|^\./|:|; } } return @them; } #========================================================================== sub _limit_glob_to_limit_re { my $self = $_[0]; my $limit_glob = $self->{'limit_glob'} || return; my $limit_re = '^' . quotemeta($limit_glob) . '$'; $limit_re =~ s/\\\?/./g; # glob "?" => "." $limit_re =~ s/\\\*/.*?/g; # glob "*" => ".*?" $limit_re =~ s/\.\*\?\$$//s; # final glob "*" => ".*?$" => "" $self->{'verbose'} and print "Turning limit_glob $limit_glob into re $limit_re\n"; # A common optimization: if(!exists($self->{'dir_prefix'}) and $limit_glob =~ m/^(?:\w+\:\:)+/s # like "File::*" or "File::Thing*" # Optimize for sane and common cases (but not things like "*::File") ) { $self->{'dir_prefix'} = join "::", $limit_glob =~ m/^(?:\w+::)+/sg; $self->{'verbose'} and print " and setting dir_prefix to $self->{'dir_prefix'}\n"; } return $limit_re; } #========================================================================== # contribution mostly from Tim Jenness sub _actual_filenames { my $dir = shift; my $fn = lc shift; opendir my $dh, $dir or return; return map { File::Spec->catdir($dir, $_) } grep { lc $_ eq $fn } readdir $dh; } sub find { my($self, $pod, @search_dirs) = @_; $self = $self->new unless ref $self; # tolerate being a class method # Check usage Carp::carp 'Usage: \$self->find($podname, ...)' unless defined $pod and length $pod; my $verbose = $self->verbose; # Split on :: and then join the name together using File::Spec my @parts = split /::/, $pod; $verbose and print "Chomping {$pod} => {@parts}\n"; #@search_dirs = File::Spec->curdir unless @search_dirs; $self->_expand_inc(\@search_dirs); # Add location of binaries such as pod2text: push @search_dirs, $Config::Config{'scriptdir'} if $self->inc; my %seen_dir; while (my $dir = shift @search_dirs ) { next unless defined $dir and length $dir; next if $seen_dir{$dir}; $seen_dir{$dir} = 1; unless(-d $dir) { print "Directory $dir does not exist\n" if $verbose; } print "Looking in directory $dir\n" if $verbose; my $fullname = File::Spec->catfile( $dir, @parts ); print "Filename is now $fullname\n" if $verbose; foreach my $ext ('', '.pod', '.pm', '.pl') { # possible extensions my $fullext = $fullname . $ext; if ( -f $fullext and $self->contains_pod($fullext) ) { print "FOUND: $fullext\n" if $verbose; if (@parts > 1 && lc $parts[0] eq 'pod' && $IS_CASE_INSENSITIVE && $ext eq '.pod') { # Well, this file could be for a program (perldoc) but we actually # want a module (Pod::Perldoc). So see if there is a .pm with the # proper casing. my $subdir = dirname $fullext; unless (grep { $fullext eq $_ } _actual_filenames $subdir, "$parts[-1].pod") { print "# Looking for alternate spelling in $subdir\n" if $verbose; # Try the .pm file. my $pm = $fullname . '.pm'; if ( -f $pm and $self->contains_pod($pm) ) { # Prefer the .pm if its case matches. if (grep { $pm eq $_ } _actual_filenames $subdir, "$parts[-1].pm") { print "FOUND: $fullext\n" if $verbose; return $pm; } } } } return $fullext; } } # Case-insensitively Look for ./pod directories and slip them in. for my $subdir ( _actual_filenames($dir, 'pod') ) { if (-d $subdir) { $verbose and print "Noticing $subdir and looking there...\n"; unshift @search_dirs, $subdir; } } } return undef; } #========================================================================== sub contains_pod { my($self, $file) = @_; my $verbose = $self->{'verbose'}; # check for one line of POD $verbose > 1 and print " Scanning $file for pod...\n"; unless( open(MAYBEPOD,"<$file") ) { print "Error: $file is unreadable: $!\n"; return undef; } sleep($SLEEPY - 1) if $SLEEPY; # avoid totally hogging the processor on OSs with poor process control local $_; while( ) { if(m/^=(head\d|pod|over|item)\b/s) { close(MAYBEPOD) || die "Bizarre error closing $file: $!\nAborting"; chomp; $verbose > 1 and print " Found some pod ($_) in $file\n"; return 1; } } close(MAYBEPOD) || die "Bizarre error closing $file: $!\nAborting"; $verbose > 1 and print " No POD in $file, skipping.\n"; return 0; } #========================================================================== sub _accessorize { # A simple-minded method-maker shift; no strict 'refs'; foreach my $attrname (@_) { *{caller() . '::' . $attrname} = sub { use strict; $Carp::CarpLevel = 1, Carp::croak( "Accessor usage: \$obj->$attrname() or \$obj->$attrname(\$new_value)" ) unless (@_ == 1 or @_ == 2) and ref $_[0]; # Read access: return $_[0]->{$attrname} if @_ == 1; # Write access: $_[0]->{$attrname} = $_[1]; return $_[0]; # RETURNS MYSELF! }; } # Ya know, they say accessories make the ensemble! return; } #========================================================================== sub _state_as_string { my $self = $_[0]; return '' unless ref $self; my @out = "{\n # State of $self ...\n"; foreach my $k (sort keys %$self) { push @out, " ", _esc($k), " => ", _esc($self->{$k}), ",\n"; } push @out, "}\n"; my $x = join '', @out; $x =~ s/^/#/mg; return $x; } sub _esc { my $in = $_[0]; return 'undef' unless defined $in; $in =~ s<([^\x20\x21\x23\x27-\x3F\x41-\x5B\x5D-\x7E])> <'\\x'.(unpack("H2",$1))>eg; return qq{"$in"}; } #========================================================================== run() unless caller; # run if "perl whatever/Search.pm" 1; #========================================================================== __END__ require 5; package Pod::Simple::DumpAsXML; $VERSION = '3.35'; use Pod::Simple (); BEGIN {@ISA = ('Pod::Simple')} use strict; use Carp (); use Text::Wrap qw(wrap); BEGIN { *DEBUG = \&Pod::Simple::DEBUG unless defined &DEBUG } sub new { my $self = shift; my $new = $self->SUPER::new(@_); $new->{'output_fh'} ||= *STDOUT{IO}; $new->accept_codes('VerbatimFormatted'); $new->keep_encoding_directive(1); return $new; } #@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ sub _handle_element_start { # ($self, $element_name, $attr_hash_r) my $fh = $_[0]{'output_fh'}; my($key, $value); DEBUG and print STDERR "++ $_[1]\n"; print $fh ' ' x ($_[0]{'indent'} || 0), "<", $_[1]; foreach my $key (sort keys %{$_[2]}) { unless($key =~ m/^~/s) { next if $key eq 'start_line' and $_[0]{'hide_line_numbers'}; _xml_escape($value = $_[2]{$key}); print $fh ' ', $key, '="', $value, '"'; } } print $fh ">\n"; $_[0]{'indent'}++; return; } sub _handle_text { DEBUG and print STDERR "== \"$_[1]\"\n"; if(length $_[1]) { my $indent = ' ' x $_[0]{'indent'}; my $text = $_[1]; _xml_escape($text); local $Text::Wrap::huge = 'overflow'; $text = wrap('', $indent, $text); print {$_[0]{'output_fh'}} $indent, $text, "\n"; } return; } sub _handle_element_end { DEBUG and print STDERR "-- $_[1]\n"; print {$_[0]{'output_fh'}} ' ' x --$_[0]{'indent'}, "\n"; return; } # . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . sub _xml_escape { foreach my $x (@_) { # Escape things very cautiously: if ($] ge 5.007_003) { $x =~ s/([^-\n\t !\#\$\%\(\)\*\+,\.\~\/\:\;=\?\@\[\\\]\^_\`\{\|\}abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789])/'&#'.(utf8::native_to_unicode(ord($1))).';'/eg; } else { # Is broken for non-ASCII platforms on early perls $x =~ s/([^-\n\t !\#\$\%\(\)\*\+,\.\~\/\:\;=\?\@\[\\\]\^_\`\{\|\}abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789])/'&#'.(ord($1)).';'/eg; } # Yes, stipulate the list without a range, so that this can work right on # all charsets that this module happens to run under. } return; } #@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ 1; __END__ require 5; package Pod::Simple::Text; use strict; use Carp (); use Pod::Simple::Methody (); use Pod::Simple (); use vars qw( @ISA $VERSION $FREAKYMODE); $VERSION = '3.35'; @ISA = ('Pod::Simple::Methody'); BEGIN { *DEBUG = defined(&Pod::Simple::DEBUG) ? \&Pod::Simple::DEBUG : sub() {0} } use Text::Wrap 98.112902 (); $Text::Wrap::huge = 'overflow'; #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ sub new { my $self = shift; my $new = $self->SUPER::new(@_); $new->{'output_fh'} ||= *STDOUT{IO}; $new->accept_target_as_text(qw( text plaintext plain )); $new->nix_X_codes(1); $new->nbsp_for_S(1); $new->{'Thispara'} = ''; $new->{'Indent'} = 0; $new->{'Indentstring'} = ' '; return $new; } #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ sub handle_text { $_[0]{'Thispara'} .= $_[1] } sub start_Para { $_[0]{'Thispara'} = '' } sub start_head1 { $_[0]{'Thispara'} = '' } sub start_head2 { $_[0]{'Thispara'} = '' } sub start_head3 { $_[0]{'Thispara'} = '' } sub start_head4 { $_[0]{'Thispara'} = '' } sub start_Verbatim { $_[0]{'Thispara'} = '' } sub start_item_bullet { $_[0]{'Thispara'} = $FREAKYMODE ? '' : '* ' } sub start_item_number { $_[0]{'Thispara'} = $FREAKYMODE ? '' : "$_[1]{'number'}. " } sub start_item_text { $_[0]{'Thispara'} = '' } sub start_over_bullet { ++$_[0]{'Indent'} } sub start_over_number { ++$_[0]{'Indent'} } sub start_over_text { ++$_[0]{'Indent'} } sub start_over_block { ++$_[0]{'Indent'} } sub end_over_bullet { --$_[0]{'Indent'} } sub end_over_number { --$_[0]{'Indent'} } sub end_over_text { --$_[0]{'Indent'} } sub end_over_block { --$_[0]{'Indent'} } # . . . . . Now the actual formatters: sub end_head1 { $_[0]->emit_par(-4) } sub end_head2 { $_[0]->emit_par(-3) } sub end_head3 { $_[0]->emit_par(-2) } sub end_head4 { $_[0]->emit_par(-1) } sub end_Para { $_[0]->emit_par( 0) } sub end_item_bullet { $_[0]->emit_par( 0) } sub end_item_number { $_[0]->emit_par( 0) } sub end_item_text { $_[0]->emit_par(-2) } sub start_L { $_[0]{'Link'} = $_[1] if $_[1]->{type} eq 'url' } sub end_L { if (my $link = delete $_[0]{'Link'}) { # Append the URL to the output unless it's already present. $_[0]{'Thispara'} .= " <$link->{to}>" unless $_[0]{'Thispara'} =~ /\b\Q$link->{to}/; } } sub emit_par { my($self, $tweak_indent) = splice(@_,0,2); my $indent = ' ' x ( 2 * $self->{'Indent'} + 4 + ($tweak_indent||0) ); # Yes, 'STRING' x NEGATIVE gives '', same as 'STRING' x 0 $self->{'Thispara'} =~ s/$Pod::Simple::shy//g; my $out = Text::Wrap::wrap($indent, $indent, $self->{'Thispara'} .= "\n"); $out =~ s/$Pod::Simple::nbsp/ /g; print {$self->{'output_fh'}} $out, "\n"; $self->{'Thispara'} = ''; return; } # . . . . . . . . . . And then off by its lonesome: sub end_Verbatim { my $self = shift; $self->{'Thispara'} =~ s/$Pod::Simple::nbsp/ /g; $self->{'Thispara'} =~ s/$Pod::Simple::shy//g; my $i = ' ' x ( 2 * $self->{'Indent'} + 4); #my $i = ' ' x (4 + $self->{'Indent'}); $self->{'Thispara'} =~ s/^/$i/mg; print { $self->{'output_fh'} } '', $self->{'Thispara'}, "\n\n" ; $self->{'Thispara'} = ''; return; } #@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ 1; __END__ use strict; package Pod::Simple::TiedOutFH; use Symbol ('gensym'); use Carp (); use vars qw($VERSION ); $VERSION = '3.35'; #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ sub handle_on { # some horrible frightening things are encapsulated in here my $class = shift; $class = ref($class) || $class; Carp::croak "Usage: ${class}->handle_on(\$somescalar)" unless @_; my $x = (defined($_[0]) and ref($_[0])) ? $_[0] : ( \( $_[0] ) )[0] ; $$x = '' unless defined $$x; #Pod::Simple::DEBUG and print STDERR "New $class handle on $x = \"$$x\"\n"; my $new = gensym(); tie *$new, $class, $x; return $new; } #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ sub TIEHANDLE { # Ties to just a scalar ref my($class, $scalar_ref) = @_; $$scalar_ref = '' unless defined $$scalar_ref; return bless \$scalar_ref, ref($class) || $class; } sub PRINT { my $it = shift; foreach my $x (@_) { $$$it .= $x } #Pod::Simple::DEBUG > 10 and print STDERR " appended to $$it = \"$$$it\"\n"; return 1; } sub FETCH { return ${$_[0]}; } sub PRINTF { my $it = shift; my $format = shift; $$$it .= sprintf $format, @_; return 1; } sub FILENO { ${ $_[0] } + 100 } # just to produce SOME number sub CLOSE { 1 } #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 1; __END__ Chole * 1 large red onion * 2 tomatillos * 4 or 5 roma tomatoes (optionally with the pulp discarded) * 1 tablespoons chopped ginger root (or more, to taste) * 2 tablespoons canola oil (or vegetable oil) * 1 tablespoon garam masala * 1/2 teaspoon red chili powder, or to taste * Salt, to taste (probably quite a bit) * 2 (15-ounce) cans chick peas or garbanzo beans, drained and rinsed * juice of one smallish lime * a dash of balsamic vinegar (to taste) * cooked rice, preferably long-grain white rice (whether plain, basmati rice, jasmine rice, or even a mild pilaf) In a blender or food processor, puree the onions, tomatoes, tomatillos, and ginger root. You can even do it with a Braun hand "mixer", if you chop things finer to start with, and work at it. In a saucepan set over moderate heat, warm the oil until hot. Add the puree and the balsamic vinegar, and cook, stirring occasionally, for 20 to 40 minutes. (Cooking it longer will make it sweeter.) Add the Garam Masala, chili powder, and cook, stirring occasionally, for 5 minutes. Add the salt and chick peas and cook, stirring, until heated through. Stir in the lime juice, and optionally one or two teaspoons of tahini. You can let it simmer longer, depending on how much softer you want the garbanzos to get. Serve over rice, like a curry. Yields 5 to 7 servings. require 5; package Pod::Simple::RTF; #sub DEBUG () {4}; #sub Pod::Simple::DEBUG () {4}; #sub Pod::Simple::PullParser::DEBUG () {4}; use strict; use vars qw($VERSION @ISA %Escape $WRAP %Tagmap); $VERSION = '3.35'; use Pod::Simple::PullParser (); BEGIN {@ISA = ('Pod::Simple::PullParser')} use Carp (); BEGIN { *DEBUG = \&Pod::Simple::DEBUG unless defined &DEBUG } $WRAP = 1 unless defined $WRAP; # These are broken for early Perls on EBCDIC; they could be fixed to work # better there, but not worth it. These are part of a larger [...] class, so # are just the strings to substitute into it, as opposed to compiled patterns. my $cntrl = '[:cntrl:]'; $cntrl = '\x00-\x1F\x7F' unless eval "qr/[$cntrl]/"; my $not_ascii = '[:^ascii:]'; $not_ascii = '\x80-\xFF' unless eval "qr/[$not_ascii]/"; #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ sub _openclose { return map {; m/^([-A-Za-z]+)=(\w[^\=]*)$/s or die "what's <$_>?"; ( $1, "{\\$2\n", "/$1", "}" ); } @_; } my @_to_accept; %Tagmap = ( # 'foo=bar' means ('foo' => '{\bar'."\n", '/foo' => '}') _openclose( 'B=cs18\b', 'I=cs16\i', 'C=cs19\f1\lang1024\noproof', 'F=cs17\i\lang1024\noproof', 'VerbatimI=cs26\i', 'VerbatimB=cs27\b', 'VerbatimBI=cs28\b\i', map {; m/^([-a-z]+)/s && push @_to_accept, $1; $_ } qw[ underline=ul smallcaps=scaps shadow=shad superscript=super subscript=sub strikethrough=strike outline=outl emboss=embo engrave=impr dotted-underline=uld dash-underline=uldash dot-dash-underline=uldashd dot-dot-dash-underline=uldashdd double-underline=uldb thick-underline=ulth word-underline=ulw wave-underline=ulwave ] # But no double-strikethrough, because MSWord can't agree with the # RTF spec on whether it's supposed to be \strikedl or \striked1 (!!!) ), # Bit of a hack here: 'L=pod' => '{\cs22\i'."\n", 'L=url' => '{\cs23\i'."\n", 'L=man' => '{\cs24\i'."\n", '/L' => '}', 'Data' => "\n", '/Data' => "\n", 'Verbatim' => "\n{\\pard\\li#rtfindent##rtfkeep#\\plain\\s20\\sa180\\f1\\fs18\\lang1024\\noproof\n", '/Verbatim' => "\n\\par}\n", 'VerbatimFormatted' => "\n{\\pard\\li#rtfindent##rtfkeep#\\plain\\s20\\sa180\\f1\\fs18\\lang1024\\noproof\n", '/VerbatimFormatted' => "\n\\par}\n", 'Para' => "\n{\\pard\\li#rtfindent#\\sa180\n", '/Para' => "\n\\par}\n", 'head1' => "\n{\\pard\\li#rtfindent#\\s31\\keepn\\sb90\\sa180\\f2\\fs#head1_halfpoint_size#\\ul{\n", '/head1' => "\n}\\par}\n", 'head2' => "\n{\\pard\\li#rtfindent#\\s32\\keepn\\sb90\\sa180\\f2\\fs#head2_halfpoint_size#\\ul{\n", '/head2' => "\n}\\par}\n", 'head3' => "\n{\\pard\\li#rtfindent#\\s33\\keepn\\sb90\\sa180\\f2\\fs#head3_halfpoint_size#\\ul{\n", '/head3' => "\n}\\par}\n", 'head4' => "\n{\\pard\\li#rtfindent#\\s34\\keepn\\sb90\\sa180\\f2\\fs#head4_halfpoint_size#\\ul{\n", '/head4' => "\n}\\par}\n", # wordpad borks on \tc\tcl1, or I'd put that in =head1 and =head2 'item-bullet' => "\n{\\pard\\li#rtfindent##rtfitemkeepn#\\sb60\\sa150\\fi-120\n", '/item-bullet' => "\n\\par}\n", 'item-number' => "\n{\\pard\\li#rtfindent##rtfitemkeepn#\\sb60\\sa150\\fi-120\n", '/item-number' => "\n\\par}\n", 'item-text' => "\n{\\pard\\li#rtfindent##rtfitemkeepn#\\sb60\\sa150\\fi-120\n", '/item-text' => "\n\\par}\n", # we don't need any styles for over-* and /over-* ); #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ sub new { my $new = shift->SUPER::new(@_); $new->nix_X_codes(1); $new->nbsp_for_S(1); $new->accept_targets( 'rtf', 'RTF' ); $new->{'Tagmap'} = {%Tagmap}; $new->accept_codes(@_to_accept); $new->accept_codes('VerbatimFormatted'); DEBUG > 2 and print STDERR "To accept: ", join(' ',@_to_accept), "\n"; $new->doc_lang( ( $ENV{'RTFDEFLANG'} || '') =~ m/^(\d{1,10})$/s ? $1 : ($ENV{'RTFDEFLANG'} || '') =~ m/^0?x([a-fA-F0-9]{1,10})$/s ? hex($1) # yes, tolerate hex! : ($ENV{'RTFDEFLANG'} || '') =~ m/^([a-fA-F0-9]{4})$/s ? hex($1) # yes, tolerate even more hex! : '1033' ); $new->head1_halfpoint_size(32); $new->head2_halfpoint_size(28); $new->head3_halfpoint_size(25); $new->head4_halfpoint_size(22); $new->codeblock_halfpoint_size(18); $new->header_halfpoint_size(17); $new->normal_halfpoint_size(25); return $new; } #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ __PACKAGE__->_accessorize( 'doc_lang', 'head1_halfpoint_size', 'head2_halfpoint_size', 'head3_halfpoint_size', 'head4_halfpoint_size', 'codeblock_halfpoint_size', 'header_halfpoint_size', 'normal_halfpoint_size', 'no_proofing_exemptions', ); #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ sub run { my $self = $_[0]; return $self->do_middle if $self->bare_output; return $self->do_beginning && $self->do_middle && $self->do_end; } #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ sub do_middle { # the main work my $self = $_[0]; my $fh = $self->{'output_fh'}; my($token, $type, $tagname, $scratch); my @stack; my @indent_stack; $self->{'rtfindent'} = 0 unless defined $self->{'rtfindent'}; while($token = $self->get_token) { if( ($type = $token->type) eq 'text' ) { if( $self->{'rtfverbatim'} ) { DEBUG > 1 and print STDERR " $type " , $token->text, " in verbatim!\n"; rtf_esc_codely($scratch = $token->text); print $fh $scratch; next; } DEBUG > 1 and print STDERR " $type " , $token->text, "\n"; $scratch = $token->text; $scratch =~ tr/\t\cb\cc/ /d; $self->{'no_proofing_exemptions'} or $scratch =~ s/(?: ^ | (?<=[\r\n\t "\[\<\(]) ) # start on whitespace, sequence-start, or quote ( # something looking like a Perl token: (?: [\$\@\:\<\*\\_]\S+ # either starting with a sigil, etc. ) | # or starting alpha, but containing anything strange: (?: [a-zA-Z'${not_ascii}]+[\$\@\:_<>\(\\\*]\S+ ) ) /\cb$1\cc/xsg ; rtf_esc($scratch); $scratch =~ s/( [^\r\n]{65} # Snare 65 characters from a line [^\r\n ]{0,50} # and finish any current word ) (\ {1,10})(?![\r\n]) # capture some spaces not at line-end /$1$2\n/gx # and put a NL before those spaces if $WRAP; # This may wrap at well past the 65th column, but not past the 120th. print $fh $scratch; } elsif( $type eq 'start' ) { DEBUG > 1 and print STDERR " +$type ",$token->tagname, " (", map("<$_> ", %{$token->attr_hash}), ")\n"; if( ($tagname = $token->tagname) eq 'Verbatim' or $tagname eq 'VerbatimFormatted' ) { ++$self->{'rtfverbatim'}; my $next = $self->get_token; next unless defined $next; my $line_count = 1; if($next->type eq 'text') { my $t = $next->text_r; while( $$t =~ m/$/mg ) { last if ++$line_count > 15; # no point in counting further } DEBUG > 3 and print STDERR " verbatim line count: $line_count\n"; } $self->unget_token($next); $self->{'rtfkeep'} = ($line_count > 15) ? '' : '\keepn' ; } elsif( $tagname =~ m/^item-/s ) { my @to_unget; my $text_count_here = 0; $self->{'rtfitemkeepn'} = ''; # Some heuristics to stop item-*'s functioning as subheadings # from getting split from the things they're subheadings for. # # It's not terribly pretty, but it really does make things pretty. # while(1) { push @to_unget, $self->get_token; pop(@to_unget), last unless defined $to_unget[-1]; # Erroneously used to be "unshift" instead of pop! Adds instead # of removes, and operates on the beginning instead of the end! if($to_unget[-1]->type eq 'text') { if( ($text_count_here += length ${$to_unget[-1]->text_r}) > 150 ){ DEBUG > 1 and print STDERR " item-* is too long to be keepn'd.\n"; last; } } elsif (@to_unget > 1 and $to_unget[-2]->type eq 'end' and $to_unget[-2]->tagname =~ m/^item-/s ) { # Bail out here, after setting rtfitemkeepn yea or nay. $self->{'rtfitemkeepn'} = '\keepn' if $to_unget[-1]->type eq 'start' and $to_unget[-1]->tagname eq 'Para'; DEBUG > 1 and printf STDERR " item-* before %s(%s) %s keepn'd.\n", $to_unget[-1]->type, $to_unget[-1]->can('tagname') ? $to_unget[-1]->tagname : '', $self->{'rtfitemkeepn'} ? "gets" : "doesn't get"; last; } elsif (@to_unget > 40) { DEBUG > 1 and print STDERR " item-* now has too many tokens (", scalar(@to_unget), (DEBUG > 4) ? (q<: >, map($_->dump, @to_unget)) : (), ") to be keepn'd.\n"; last; # give up } # else keep while'ing along } # Now put it aaaaall back... $self->unget_token(@to_unget); } elsif( $tagname =~ m/^over-/s ) { push @stack, $1; push @indent_stack, int($token->attr('indent') * 4 * $self->normal_halfpoint_size); DEBUG and print STDERR "Indenting over $indent_stack[-1] twips.\n"; $self->{'rtfindent'} += $indent_stack[-1]; } elsif ($tagname eq 'L') { $tagname .= '=' . ($token->attr('type') || 'pod'); } elsif ($tagname eq 'Data') { my $next = $self->get_token; next unless defined $next; unless( $next->type eq 'text' ) { $self->unget_token($next); next; } DEBUG and print STDERR " raw text ", $next->text, "\n"; printf $fh "\n" . $next->text . "\n"; next; } defined($scratch = $self->{'Tagmap'}{$tagname}) or next; $scratch =~ s/\#([^\#]+)\#/${$self}{$1}/g; # interpolate print $fh $scratch; if ($tagname eq 'item-number') { print $fh $token->attr('number'), ". \n"; } elsif ($tagname eq 'item-bullet') { print $fh "\\'", ord("_"), "\n"; #for funky testing: print $fh '', rtf_esc("\x{4E4B}\x{9053}"); } } elsif( $type eq 'end' ) { DEBUG > 1 and print STDERR " -$type ",$token->tagname,"\n"; if( ($tagname = $token->tagname) =~ m/^over-/s ) { DEBUG and print STDERR "Indenting back $indent_stack[-1] twips.\n"; $self->{'rtfindent'} -= pop @indent_stack; pop @stack; } elsif( $tagname eq 'Verbatim' or $tagname eq 'VerbatimFormatted') { --$self->{'rtfverbatim'}; } defined($scratch = $self->{'Tagmap'}{"/$tagname"}) or next; $scratch =~ s/\#([^\#]+)\#/${$self}{$1}/g; # interpolate print $fh $scratch; } } return 1; } #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ sub do_beginning { my $self = $_[0]; my $fh = $self->{'output_fh'}; return print $fh join '', $self->doc_init, $self->font_table, $self->stylesheet, $self->color_table, $self->doc_info, $self->doc_start, "\n" ; } sub do_end { my $self = $_[0]; my $fh = $self->{'output_fh'}; return print $fh '}'; # that should do it } ########################################################################### sub stylesheet { return sprintf <<'END', {\stylesheet {\snext0 Normal;} {\*\cs10 \additive Default Paragraph Font;} {\*\cs16 \additive \i \sbasedon10 pod-I;} {\*\cs17 \additive \i\lang1024\noproof \sbasedon10 pod-F;} {\*\cs18 \additive \b \sbasedon10 pod-B;} {\*\cs19 \additive \f1\lang1024\noproof\sbasedon10 pod-C;} {\s20\ql \li0\ri0\sa180\widctlpar\f1\fs%s\lang1024\noproof\sbasedon0 \snext0 pod-codeblock;} {\*\cs21 \additive \lang1024\noproof \sbasedon10 pod-computerese;} {\*\cs22 \additive \i\lang1024\noproof\sbasedon10 pod-L-pod;} {\*\cs23 \additive \i\lang1024\noproof\sbasedon10 pod-L-url;} {\*\cs24 \additive \i\lang1024\noproof\sbasedon10 pod-L-man;} {\*\cs25 \additive \f1\lang1024\noproof\sbasedon0 pod-codelbock-plain;} {\*\cs26 \additive \f1\lang1024\noproof\sbasedon25 pod-codelbock-ital;} {\*\cs27 \additive \f1\lang1024\noproof\sbasedon25 pod-codelbock-bold;} {\*\cs28 \additive \f1\lang1024\noproof\sbasedon25 pod-codelbock-bold-ital;} {\s31\ql \keepn\sb90\sa180\f2\fs%s\ul\sbasedon0 \snext0 pod-head1;} {\s32\ql \keepn\sb90\sa180\f2\fs%s\ul\sbasedon0 \snext0 pod-head2;} {\s33\ql \keepn\sb90\sa180\f2\fs%s\ul\sbasedon0 \snext0 pod-head3;} {\s34\ql \keepn\sb90\sa180\f2\fs%s\ul\sbasedon0 \snext0 pod-head4;} } END $_[0]->codeblock_halfpoint_size(), $_[0]->head1_halfpoint_size(), $_[0]->head2_halfpoint_size(), $_[0]->head3_halfpoint_size(), $_[0]->head4_halfpoint_size(), ; } ########################################################################### # Override these as necessary for further customization sub font_table { return <<'END'; # text font, code font, heading font {\fonttbl {\f0\froman Times New Roman;} {\f1\fmodern Courier New;} {\f2\fswiss Arial;} } END } sub doc_init { return <<'END'; {\rtf1\ansi\deff0 END } sub color_table { return <<'END'; {\colortbl;\red255\green0\blue0;\red0\green0\blue255;} END } sub doc_info { my $self = $_[0]; my $class = ref($self) || $self; my $tag = __PACKAGE__ . ' ' . $VERSION; unless($class eq __PACKAGE__) { $tag = " ($tag)"; $tag = " v" . $self->VERSION . $tag if defined $self->VERSION; $tag = $class . $tag; } return sprintf <<'END', {\info{\doccomm %s using %s v%s under Perl v%s at %s GMT} {\author [see doc]}{\company [see doc]}{\operator [see doc]} } END # None of the following things should need escaping, I dare say! $tag, $ISA[0], $ISA[0]->VERSION(), $], scalar(gmtime), ; } sub doc_start { my $self = $_[0]; my $title = $self->get_short_title(); DEBUG and print STDERR "Short Title: <$title>\n"; $title .= ' ' if length $title; $title =~ s/ *$/ /s; $title =~ s/^ //s; $title =~ s/ $/, /s; # make sure it ends in a comma and a space, unless it's 0-length my $is_obviously_module_name; $is_obviously_module_name = 1 if $title =~ m/^\S+$/s and $title =~ m/::/s; # catches the most common case, at least DEBUG and print STDERR "Title0: <$title>\n"; $title = rtf_esc($title); DEBUG and print STDERR "Title1: <$title>\n"; $title = '\lang1024\noproof ' . $title if $is_obviously_module_name; return sprintf <<'END', \deflang%s\plain\lang%s\widowctrl {\header\pard\qr\plain\f2\fs%s %s p.\chpgn\par} \fs%s END ($self->doc_lang) x 2, $self->header_halfpoint_size, $title, $self->normal_halfpoint_size, ; } #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ #------------------------------------------------------------------------- use integer; sub rtf_esc { my $x; # scratch if(!defined wantarray) { # void context: alter in-place! for(@_) { s/([F${cntrl}\-\\\{\}${not_ascii}])/$Escape{$1}/g; # ESCAPER s/([^\x00-\xFF])/'\\uc1\\u'.((ord($1)<32768)?ord($1):(ord($1)-65536)).'?'/eg; } return; } elsif(wantarray) { # return an array return map {; ($x = $_) =~ s/([F${cntrl}\-\\\{\}${not_ascii}])/$Escape{$1}/g; # ESCAPER $x =~ s/([^\x00-\xFF])/'\\uc1\\u'.((ord($1)<32768)?ord($1):(ord($1)-65536)).'?'/eg; $x; } @_; } else { # return a single scalar ($x = ((@_ == 1) ? $_[0] : join '', @_) ) =~ s/([F${cntrl}\-\\\{\}${not_ascii}])/$Escape{$1}/g; # ESCAPER # Escape \, {, }, -, control chars, and 7f-ff. $x =~ s/([^\x00-\xFF])/'\\uc1\\u'.((ord($1)<32768)?ord($1):(ord($1)-65536)).'?'/eg; return $x; } } sub rtf_esc_codely { # Doesn't change "-" to hard-hyphen, nor apply computerese style-smarts. # We don't want to change the "-" to hard-hyphen, because we want to # be able to paste this into a file and run it without there being # dire screaming about the mysterious hard-hyphen character (which # looks just like a normal dash character). my $x; # scratch if(!defined wantarray) { # void context: alter in-place! for(@_) { s/([F${cntrl}\\\{\}${not_ascii}])/$Escape{$1}/g; # ESCAPER s/([^\x00-\xFF])/'\\uc1\\u'.((ord($1)<32768)?ord($1):(ord($1)-65536)).'?'/eg; } return; } elsif(wantarray) { # return an array return map {; ($x = $_) =~ s/([F${cntrl}\\\{\}${not_ascii}])/$Escape{$1}/g; # ESCAPER $x =~ s/([^\x00-\xFF])/'\\uc1\\u'.((ord($1)<32768)?ord($1):(ord($1)-65536)).'?'/eg; $x; } @_; } else { # return a single scalar ($x = ((@_ == 1) ? $_[0] : join '', @_) ) =~ s/([F${cntrl}\\\{\}${not_ascii}])/$Escape{$1}/g; # ESCAPER # Escape \, {, }, -, control chars, and 7f-ff. $x =~ s/([^\x00-\xFF])/'\\uc1\\u'.((ord($1)<32768)?ord($1):(ord($1)-65536)).'?'/eg; return $x; } } %Escape = ( (($] lt 5.007_003) # Broken for non-ASCII on early Perls ? (map( (chr($_),chr($_)), # things not apparently needing escaping 0x20 .. 0x7E ), map( (chr($_),sprintf("\\'%02x", $_)), # apparently escapeworthy things 0x00 .. 0x1F, 0x5c, 0x7b, 0x7d, 0x7f .. 0xFF, 0x46)) : (map( (chr(utf8::unicode_to_native($_)),chr(utf8::unicode_to_native($_))), 0x20 .. 0x7E ), map( (chr($_),sprintf("\\'%02x", utf8::unicode_to_native($_))), 0x00 .. 0x1F, 0x5c, 0x7b, 0x7d, 0x7f .. 0xFF, 0x46))), # We get to escape out 'F' so that we can send RTF files thru the mail # without the slightest worry that paragraphs beginning with "From" # will get munged. # And some refinements: "\r" => "\n", "\cj" => "\n", "\n" => "\n\\line ", "\t" => "\\tab ", # Tabs (altho theoretically raw \t's are okay) "\f" => "\n\\page\n", # Formfeed "-" => "\\_", # Turn plaintext '-' into a non-breaking hyphen $Pod::Simple::nbsp => "\\~", # Latin-1 non-breaking space $Pod::Simple::shy => "\\-", # Latin-1 soft (optional) hyphen # CRAZY HACKS: "\n" => "\\line\n", "\r" => "\n", "\cb" => "{\n\\cs21\\lang1024\\noproof ", # \\cf1 "\cc" => "}", ); 1; __END__ package Pod::Simple::BlackBox; # # "What's in the box?" "Pain." # ########################################################################### # # This is where all the scary things happen: parsing lines into # paragraphs; and then into directives, verbatims, and then also # turning formatting sequences into treelets. # # Are you really sure you want to read this code? # #----------------------------------------------------------------------------- # # The basic work of this module Pod::Simple::BlackBox is doing the dirty work # of parsing Pod into treelets (generally one per non-verbatim paragraph), and # to call the proper callbacks on the treelets. # # Every node in a treelet is a ['name', {attrhash}, ...children...] use integer; # vroom! use strict; use Carp (); use vars qw($VERSION ); $VERSION = '3.35'; #use constant DEBUG => 7; BEGIN { require Pod::Simple; *DEBUG = \&Pod::Simple::DEBUG unless defined &DEBUG } # Matches a character iff the character will have a different meaning # if we choose CP1252 vs UTF-8 if there is no =encoding line. # This is broken for early Perls on non-ASCII platforms. my $non_ascii_re = eval "qr/[[:^ascii:]]/"; $non_ascii_re = qr/[\x80-\xFF]/ if ! defined $non_ascii_re; my $utf8_bom; if (($] ge 5.007_003)) { $utf8_bom = "\x{FEFF}"; utf8::encode($utf8_bom); } else { $utf8_bom = "\xEF\xBB\xBF"; # No EBCDIC BOM detection for early Perls. } #@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ sub parse_line { shift->parse_lines(@_) } # alias # - - - Turn back now! Run away! - - - sub parse_lines { # Usage: $parser->parse_lines(@lines) # an undef means end-of-stream my $self = shift; my $code_handler = $self->{'code_handler'}; my $cut_handler = $self->{'cut_handler'}; my $wl_handler = $self->{'whiteline_handler'}; $self->{'line_count'} ||= 0; my $scratch; DEBUG > 4 and print STDERR "# Parsing starting at line ", $self->{'line_count'}, ".\n"; DEBUG > 5 and print STDERR "# About to parse lines: ", join(' ', map defined($_) ? "[$_]" : "EOF", @_), "\n"; my $paras = ($self->{'paras'} ||= []); # paragraph buffer. Because we need to defer processing of =over # directives and verbatim paragraphs. We call _ponder_paragraph_buffer # to process this. $self->{'pod_para_count'} ||= 0; my $line; foreach my $source_line (@_) { if( $self->{'source_dead'} ) { DEBUG > 4 and print STDERR "# Source is dead.\n"; last; } unless( defined $source_line ) { DEBUG > 4 and print STDERR "# Undef-line seen.\n"; push @$paras, ['~end', {'start_line' => $self->{'line_count'}}]; push @$paras, $paras->[-1], $paras->[-1]; # So that it definitely fills the buffer. $self->{'source_dead'} = 1; $self->_ponder_paragraph_buffer; next; } if( $self->{'line_count'}++ ) { ($line = $source_line) =~ tr/\n\r//d; # If we don't have two vars, we'll end up with that there # tr/// modding the (potentially read-only) original source line! } else { DEBUG > 2 and print STDERR "First line: [$source_line]\n"; if( ($line = $source_line) =~ s/^$utf8_bom//s ) { DEBUG and print STDERR "UTF-8 BOM seen. Faking a '=encoding utf8'.\n"; $self->_handle_encoding_line( "=encoding utf8" ); delete $self->{'_processed_encoding'}; $line =~ tr/\n\r//d; } elsif( $line =~ s/^\xFE\xFF//s ) { DEBUG and print STDERR "Big-endian UTF-16 BOM seen. Aborting parsing.\n"; $self->scream( $self->{'line_count'}, "UTF16-BE Byte Encoding Mark found; but Pod::Simple v$Pod::Simple::VERSION doesn't implement UTF16 yet." ); splice @_; push @_, undef; next; # TODO: implement somehow? } elsif( $line =~ s/^\xFF\xFE//s ) { DEBUG and print STDERR "Little-endian UTF-16 BOM seen. Aborting parsing.\n"; $self->scream( $self->{'line_count'}, "UTF16-LE Byte Encoding Mark found; but Pod::Simple v$Pod::Simple::VERSION doesn't implement UTF16 yet." ); splice @_; push @_, undef; next; # TODO: implement somehow? } else { DEBUG > 2 and print STDERR "First line is BOM-less.\n"; ($line = $source_line) =~ tr/\n\r//d; } } if(!$self->{'parse_characters'} && !$self->{'encoding'} && ($self->{'in_pod'} || $line =~ /^=/s) && $line =~ /$non_ascii_re/ ) { my $encoding; # No =encoding line, and we are at the first line in the input that # contains a non-ascii byte, that is one whose meaning varies depending # on whether the file is encoded in UTF-8 or CP1252, which are the two # possibilities permitted by the pod spec. (ASCII is assumed if the # file only contains ASCII bytes.) In order to process this line, we # need to figure out what encoding we will use for the file. # # Strictly speaking ISO 8859-1 (Latin 1) refers to the code points # 160-255, but it is used here, as it often colloquially is, to refer to # the complete set of code points 0-255, including ASCII (0-127), the C1 # controls (128-159), and strict Latin 1 (160-255). # # CP1252 is effectively a superset of Latin 1, because it differs only # from colloquial 8859-1 in the C1 controls, which are very unlikely to # actually be present in 8859-1 files, so can be used for other purposes # without conflict. CP 1252 uses most of them for graphic characters. # # Note that all ASCII-range bytes represent their corresponding code # points in CP1252 and UTF-8. In ASCII platform UTF-8 all other code # points require multiple (non-ASCII) bytes to represent. (A separate # paragraph for EBCDIC is below.) The multi-byte representation is # quite structured. If we find an isolated byte that requires multiple # bytes to represent in UTF-8, we know that the encoding is not UTF-8. # If we find a sequence of bytes that violates the UTF-8 structure, we # also can presume the encoding isn't UTF-8, and hence must be 1252. # # But there are ambiguous cases where we could guess wrong. If so, the # user will end up having to supply an =encoding line. We use all # readily available information to improve our chances of guessing # right. The odds of something not being UTF-8, but still passing a # UTF-8 validity test go down very rapidly with increasing length of the # sequence. Therefore we look at all the maximal length non-ascii # sequences on the line. If any of the sequences can't be UTF-8, we # quit there and choose CP1252. If all could be UTF-8, we guess UTF-8. # # On EBCDIC platforms, the situation is somewhat different. In # UTF-EBCDIC, not only do ASCII-range bytes represent their code points, # but so do the bytes that are for the C1 controls. Recall that these # correspond to the unused portion of 8859-1 that 1252 mostly takes # over. That means that there are fewer code points that are # represented by multi-bytes. But, note that the these controls are # very unlikely to be in pod text. So if we encounter one of them, it # means that it is quite likely CP1252 and not UTF-8. The net result is # the same code below is used for both platforms. while ($line =~ m/($non_ascii_re+)/g) { my $non_ascii_seq = $1; if (length $non_ascii_seq == 1) { $encoding = 'CP1252'; goto guessed; } elsif ($] ge 5.007_003) { # On Perls that have this function, we can see if the sequence is # valid UTF-8 or not. my $is_utf8; { no warnings 'utf8'; $is_utf8 = utf8::decode($non_ascii_seq); } if (! $is_utf8) { $encoding = 'CP1252'; goto guessed; } } elsif (ord("A") == 65) { # An early Perl, ASCII platform # Without utf8::decode, it's a lot harder to do a rigorous check # (though some early releases had a different function that # accomplished the same thing). Since these are ancient Perls, not # likely to be in use today, we take the easy way out, and look at # just the first two bytes of the sequence to see if they are the # start of a UTF-8 character. In ASCII UTF-8, continuation bytes # must be between 0x80 and 0xBF. Start bytes can range from 0xC2 # through 0xFF, but anything above 0xF4 is not Unicode, and hence # extremely unlikely to be in a pod. if ($non_ascii_seq !~ /^[\xC2-\xF4][\x80-\xBF]/) { $encoding = 'CP1252'; goto guessed; } # We don't bother doing anything special for EBCDIC on early Perls. # If there is a solitary variant, CP1252 will be chosen; otherwise # UTF-8. } } # End of loop through all variant sequences on the line # All sequences in the line could be UTF-8. Guess that. $encoding = 'UTF-8'; guessed: $self->_handle_encoding_line( "=encoding $encoding" ); delete $self->{'_processed_encoding'}; $self->{'_transcoder'} && $self->{'_transcoder'}->($line); my ($word) = $line =~ /(\S*$non_ascii_re\S*)/; $self->whine( $self->{'line_count'}, "Non-ASCII character seen before =encoding in '$word'. Assuming $encoding" ); } DEBUG > 5 and print STDERR "# Parsing line: [$line]\n"; if(!$self->{'in_pod'}) { if($line =~ m/^=([a-zA-Z][a-zA-Z0-9]*)(?:\s|$)/s) { if($1 eq 'cut') { $self->scream( $self->{'line_count'}, "=cut found outside a pod block. Skipping to next block." ); ## Before there were errata sections in the world, it was ## least-pessimal to abort processing the file. But now we can ## just barrel on thru (but still not start a pod block). #splice @_; #push @_, undef; next; } else { $self->{'in_pod'} = $self->{'start_of_pod_block'} = $self->{'last_was_blank'} = 1; # And fall thru to the pod-mode block further down } } else { DEBUG > 5 and print STDERR "# It's a code-line.\n"; $code_handler->(map $_, $line, $self->{'line_count'}, $self) if $code_handler; # Note: this may cause code to be processed out of order relative # to pods, but in order relative to cuts. # Note also that we haven't yet applied the transcoding to $line # by time we call $code_handler! if( $line =~ m/^#\s*line\s+(\d+)\s*(?:\s"([^"]+)")?\s*$/ ) { # That RE is from perlsyn, section "Plain Old Comments (Not!)", #$fname = $2 if defined $2; #DEBUG > 1 and defined $2 and print STDERR "# Setting fname to \"$fname\"\n"; DEBUG > 1 and print STDERR "# Setting nextline to $1\n"; $self->{'line_count'} = $1 - 1; } next; } } # . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . # Else we're in pod mode: # Apply any necessary transcoding: $self->{'_transcoder'} && $self->{'_transcoder'}->($line); # HERE WE CATCH =encoding EARLY! if( $line =~ m/^=encoding\s+\S+\s*$/s ) { next if $self->parse_characters; # Ignore this line $line = $self->_handle_encoding_line( $line ); } if($line =~ m/^=cut/s) { # here ends the pod block, and therefore the previous pod para DEBUG > 1 and print STDERR "Noting =cut at line ${$self}{'line_count'}\n"; $self->{'in_pod'} = 0; # ++$self->{'pod_para_count'}; $self->_ponder_paragraph_buffer(); # by now it's safe to consider the previous paragraph as done. $cut_handler->(map $_, $line, $self->{'line_count'}, $self) if $cut_handler; # TODO: add to docs: Note: this may cause cuts to be processed out # of order relative to pods, but in order relative to code. } elsif($line =~ m/^(\s*)$/s) { # it's a blank line if (defined $1 and $1 =~ /[^\S\r\n]/) { # it's a white line $wl_handler->(map $_, $line, $self->{'line_count'}, $self) if $wl_handler; } if(!$self->{'start_of_pod_block'} and @$paras and $paras->[-1][0] eq '~Verbatim') { DEBUG > 1 and print STDERR "Saving blank line at line ${$self}{'line_count'}\n"; push @{$paras->[-1]}, $line; } # otherwise it's not interesting if(!$self->{'start_of_pod_block'} and !$self->{'last_was_blank'}) { DEBUG > 1 and print STDERR "Noting para ends with blank line at ${$self}{'line_count'}\n"; } $self->{'last_was_blank'} = 1; } elsif($self->{'last_was_blank'}) { # A non-blank line starting a new para... if($line =~ m/^(=[a-zA-Z][a-zA-Z0-9]*)(?:\s+|$)(.*)/s) { # THIS IS THE ONE PLACE WHERE WE CONSTRUCT NEW DIRECTIVE OBJECTS my $new = [$1, {'start_line' => $self->{'line_count'}}, $2]; # Note that in "=head1 foo", the WS is lost. # Example: ['=head1', {'start_line' => 123}, ' foo'] ++$self->{'pod_para_count'}; $self->_ponder_paragraph_buffer(); # by now it's safe to consider the previous paragraph as done. push @$paras, $new; # the new incipient paragraph DEBUG > 1 and print STDERR "Starting new ${$paras}[-1][0] para at line ${$self}{'line_count'}\n"; } elsif($line =~ m/^\s/s) { if(!$self->{'start_of_pod_block'} and @$paras and $paras->[-1][0] eq '~Verbatim') { DEBUG > 1 and print STDERR "Resuming verbatim para at line ${$self}{'line_count'}\n"; push @{$paras->[-1]}, $line; } else { ++$self->{'pod_para_count'}; $self->_ponder_paragraph_buffer(); # by now it's safe to consider the previous paragraph as done. DEBUG > 1 and print STDERR "Starting verbatim para at line ${$self}{'line_count'}\n"; push @$paras, ['~Verbatim', {'start_line' => $self->{'line_count'}}, $line]; } } else { ++$self->{'pod_para_count'}; $self->_ponder_paragraph_buffer(); # by now it's safe to consider the previous paragraph as done. push @$paras, ['~Para', {'start_line' => $self->{'line_count'}}, $line]; DEBUG > 1 and print STDERR "Starting plain para at line ${$self}{'line_count'}\n"; } $self->{'last_was_blank'} = $self->{'start_of_pod_block'} = 0; } else { # It's a non-blank line /continuing/ the current para if(@$paras) { DEBUG > 2 and print STDERR "Line ${$self}{'line_count'} continues current paragraph\n"; push @{$paras->[-1]}, $line; } else { # Unexpected case! die "Continuing a paragraph but \@\$paras is empty?"; } $self->{'last_was_blank'} = $self->{'start_of_pod_block'} = 0; } } # ends the big while loop DEBUG > 1 and print STDERR (pretty(@$paras), "\n"); return $self; } #@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ sub _handle_encoding_line { my($self, $line) = @_; return if $self->parse_characters; # The point of this routine is to set $self->{'_transcoder'} as indicated. return $line unless $line =~ m/^=encoding\s+(\S+)\s*$/s; DEBUG > 1 and print STDERR "Found an encoding line \"=encoding $1\"\n"; my $e = $1; my $orig = $e; push @{ $self->{'encoding_command_reqs'} }, "=encoding $orig"; my $enc_error; # Cf. perldoc Encode and perldoc Encode::Supported require Pod::Simple::Transcode; if( $self->{'encoding'} ) { my $norm_current = $self->{'encoding'}; my $norm_e = $e; foreach my $that ($norm_current, $norm_e) { $that = lc($that); $that =~ s/[-_]//g; } if($norm_current eq $norm_e) { DEBUG > 1 and print STDERR "The '=encoding $orig' line is ", "redundant. ($norm_current eq $norm_e). Ignoring.\n"; $enc_error = ''; # But that doesn't necessarily mean that the earlier one went okay } else { $enc_error = "Encoding is already set to " . $self->{'encoding'}; DEBUG > 1 and print STDERR $enc_error; } } elsif ( # OK, let's turn on the encoding do { DEBUG > 1 and print STDERR " Setting encoding to $e\n"; $self->{'encoding'} = $e; 1; } and $e eq 'HACKRAW' ) { DEBUG and print STDERR " Putting in HACKRAW (no-op) encoding mode.\n"; } elsif( Pod::Simple::Transcode::->encoding_is_available($e) ) { die($enc_error = "WHAT? _transcoder is already set?!") if $self->{'_transcoder'}; # should never happen require Pod::Simple::Transcode; $self->{'_transcoder'} = Pod::Simple::Transcode::->make_transcoder($e); eval { my @x = ('', "abc", "123"); $self->{'_transcoder'}->(@x); }; $@ && die( $enc_error = "Really unexpected error setting up encoding $e: $@\nAborting" ); $self->{'detected_encoding'} = $e; } else { my @supported = Pod::Simple::Transcode::->all_encodings; # Note unsupported, and complain DEBUG and print STDERR " Encoding [$e] is unsupported.", "\nSupporteds: @supported\n"; my $suggestion = ''; # Look for a near match: my $norm = lc($e); $norm =~ tr[-_][]d; my $n; foreach my $enc (@supported) { $n = lc($enc); $n =~ tr[-_][]d; next unless $n eq $norm; $suggestion = " (Maybe \"$e\" should be \"$enc\"?)"; last; } my $encmodver = Pod::Simple::Transcode::->encmodver; $enc_error = join '' => "This document probably does not appear as it should, because its ", "\"=encoding $e\" line calls for an unsupported encoding.", $suggestion, " [$encmodver\'s supported encodings are: @supported]" ; $self->scream( $self->{'line_count'}, $enc_error ); } push @{ $self->{'encoding_command_statuses'} }, $enc_error; if (defined($self->{'_processed_encoding'})) { # Double declaration. $self->scream( $self->{'line_count'}, 'Cannot have multiple =encoding directives'); } $self->{'_processed_encoding'} = $orig; return $line; } # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - sub _handle_encoding_second_level { # By time this is called, the encoding (if well formed) will already # have been acted one. my($self, $para) = @_; my @x = @$para; my $content = join ' ', splice @x, 2; $content =~ s/^\s+//s; $content =~ s/\s+$//s; DEBUG > 2 and print STDERR "Ogling encoding directive: =encoding $content\n"; if (defined($self->{'_processed_encoding'})) { #if($content ne $self->{'_processed_encoding'}) { # Could it happen? #} delete $self->{'_processed_encoding'}; # It's already been handled. Check for errors. if(! $self->{'encoding_command_statuses'} ) { DEBUG > 2 and print STDERR " CRAZY ERROR: It wasn't really handled?!\n"; } elsif( $self->{'encoding_command_statuses'}[-1] ) { $self->whine( $para->[1]{'start_line'}, sprintf "Couldn't do %s: %s", $self->{'encoding_command_reqs' }[-1], $self->{'encoding_command_statuses'}[-1], ); } else { DEBUG > 2 and print STDERR " (Yup, it was successfully handled already.)\n"; } } else { # Otherwise it's a syntax error $self->whine( $para->[1]{'start_line'}, "Invalid =encoding syntax: $content" ); } return; } #~`~`~`~`~`~`~`~`~`~`~`~`~`~`~`~`~`~`~`~`~`~`~`~`~`~`~`~`~`~`~`~`~`~`~`~`~` { my $m = -321; # magic line number sub _gen_errata { my $self = $_[0]; # Return 0 or more fake-o paragraphs explaining the accumulated # errors on this document. return() unless $self->{'errata'} and keys %{$self->{'errata'}}; my @out; foreach my $line (sort {$a <=> $b} keys %{$self->{'errata'}}) { push @out, ['=item', {'start_line' => $m}, "Around line $line:"], map( ['~Para', {'start_line' => $m, '~cooked' => 1}, #['~Top', {'start_line' => $m}, $_ #] ], @{$self->{'errata'}{$line}} ) ; } # TODO: report of unknown entities? unrenderable characters? unshift @out, ['=head1', {'start_line' => $m, 'errata' => 1}, 'POD ERRORS'], ['~Para', {'start_line' => $m, '~cooked' => 1, 'errata' => 1}, "Hey! ", ['B', {}, 'The above document had some coding errors, which are explained below:' ] ], ['=over', {'start_line' => $m, 'errata' => 1}, ''], ; push @out, ['=back', {'start_line' => $m, 'errata' => 1}, ''], ; DEBUG and print STDERR "\n<<\n", pretty(\@out), "\n>>\n\n"; return @out; } } #@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ ############################################################################## ## ## stop reading now stop reading now stop reading now stop reading now stop ## ## HERE IT BECOMES REALLY SCARY ## ## stop reading now stop reading now stop reading now stop reading now stop ## ############################################################################## sub _ponder_paragraph_buffer { # Para-token types as found in the buffer. # ~Verbatim, ~Para, ~end, =head1..4, =for, =begin, =end, # =over, =back, =item # and the null =pod (to be complained about if over one line) # # "~data" paragraphs are something we generate at this level, depending on # a currently open =over region # Events fired: Begin and end for: # directivename (like head1 .. head4), item, extend, # for (from =begin...=end, =for), # over-bullet, over-number, over-text, over-block, # item-bullet, item-number, item-text, # Document, # Data, Para, Verbatim # B, C, longdirname (TODO -- wha?), etc. for all directives # my $self = $_[0]; my $paras; return unless @{$paras = $self->{'paras'}}; my $curr_open = ($self->{'curr_open'} ||= []); my $scratch; DEBUG > 10 and print STDERR "# Paragraph buffer: <<", pretty($paras), ">>\n"; # We have something in our buffer. So apparently the document has started. unless($self->{'doc_has_started'}) { $self->{'doc_has_started'} = 1; my $starting_contentless; $starting_contentless = ( !@$curr_open and @$paras and ! grep $_->[0] ne '~end', @$paras # i.e., if the paras is all ~ends ) ; DEBUG and print STDERR "# Starting ", $starting_contentless ? 'contentless' : 'contentful', " document\n" ; $self->_handle_element_start( ($scratch = 'Document'), { 'start_line' => $paras->[0][1]{'start_line'}, $starting_contentless ? ( 'contentless' => 1 ) : (), }, ); } my($para, $para_type); while(@$paras) { last if @$paras == 1 and ( $paras->[0][0] eq '=over' or $paras->[0][0] eq '~Verbatim' or $paras->[0][0] eq '=item' ) ; # Those're the three kinds of paragraphs that require lookahead. # Actually, an "=item Foo" inside an region # and any =item inside an region (rare) # don't require any lookahead, but all others (bullets # and numbers) do. # TODO: whinge about many kinds of directives in non-resolving =for regions? # TODO: many? like what? =head1 etc? $para = shift @$paras; $para_type = $para->[0]; DEBUG > 1 and print STDERR "Pondering a $para_type paragraph, given the stack: (", $self->_dump_curr_open(), ")\n"; if($para_type eq '=for') { next if $self->_ponder_for($para,$curr_open,$paras); } elsif($para_type eq '=begin') { next if $self->_ponder_begin($para,$curr_open,$paras); } elsif($para_type eq '=end') { next if $self->_ponder_end($para,$curr_open,$paras); } elsif($para_type eq '~end') { # The virtual end-document signal next if $self->_ponder_doc_end($para,$curr_open,$paras); } # ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ #~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ if(grep $_->[1]{'~ignore'}, @$curr_open) { DEBUG > 1 and print STDERR "Skipping $para_type paragraph because in ignore mode.\n"; next; } #~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ # ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ if($para_type eq '=pod') { $self->_ponder_pod($para,$curr_open,$paras); } elsif($para_type eq '=over') { next if $self->_ponder_over($para,$curr_open,$paras); } elsif($para_type eq '=back') { next if $self->_ponder_back($para,$curr_open,$paras); } else { # All non-magical codes!!! # Here we start using $para_type for our own twisted purposes, to # mean how it should get treated, not as what the element name # should be. DEBUG > 1 and print STDERR "Pondering non-magical $para_type\n"; my $i; # Enforce some =headN discipline if($para_type =~ m/^=head\d$/s and ! $self->{'accept_heads_anywhere'} and @$curr_open and $curr_open->[-1][0] eq '=over' ) { DEBUG > 2 and print STDERR "'=$para_type' inside an '=over'!\n"; $self->whine( $para->[1]{'start_line'}, "You forgot a '=back' before '$para_type'" ); unshift @$paras, ['=back', {}, ''], $para; # close the =over next; } if($para_type eq '=item') { my $over; unless(@$curr_open and $over = (grep { $_->[0] eq '=over' } @$curr_open)[-1]) { $self->whine( $para->[1]{'start_line'}, "'=item' outside of any '=over'" ); unshift @$paras, ['=over', {'start_line' => $para->[1]{'start_line'}}, ''], $para ; next; } my $over_type = $over->[1]{'~type'}; if(!$over_type) { # Shouldn't happen1 die "Typeless over in stack, starting at line " . $over->[1]{'start_line'}; } elsif($over_type eq 'block') { unless($curr_open->[-1][1]{'~bitched_about'}) { $curr_open->[-1][1]{'~bitched_about'} = 1; $self->whine( $curr_open->[-1][1]{'start_line'}, "You can't have =items (as at line " . $para->[1]{'start_line'} . ") unless the first thing after the =over is an =item" ); } # Just turn it into a paragraph and reconsider it $para->[0] = '~Para'; unshift @$paras, $para; next; } elsif($over_type eq 'text') { my $item_type = $self->_get_item_type($para); # That kills the content of the item if it's a number or bullet. DEBUG and print STDERR " Item is of type ", $para->[0], " under $over_type\n"; if($item_type eq 'text') { # Nothing special needs doing for 'text' } elsif($item_type eq 'number' or $item_type eq 'bullet') { $self->whine( $para->[1]{'start_line'}, "Expected text after =item, not a $item_type" ); # Undo our clobbering: push @$para, $para->[1]{'~orig_content'}; delete $para->[1]{'number'}; # Only a PROPER item-number element is allowed # to have a number attribute. } else { die "Unhandled item type $item_type"; # should never happen } # =item-text thingies don't need any assimilation, it seems. } elsif($over_type eq 'number') { my $item_type = $self->_get_item_type($para); # That kills the content of the item if it's a number or bullet. DEBUG and print STDERR " Item is of type ", $para->[0], " under $over_type\n"; my $expected_value = ++ $curr_open->[-1][1]{'~counter'}; if($item_type eq 'bullet') { # Hm, it's not numeric. Correct for this. $para->[1]{'number'} = $expected_value; $self->whine( $para->[1]{'start_line'}, "Expected '=item $expected_value'" ); push @$para, $para->[1]{'~orig_content'}; # restore the bullet, blocking the assimilation of next para } elsif($item_type eq 'text') { # Hm, it's not numeric. Correct for this. $para->[1]{'number'} = $expected_value; $self->whine( $para->[1]{'start_line'}, "Expected '=item $expected_value'" ); # Text content will still be there and will block next ~Para } elsif($item_type ne 'number') { die "Unknown item type $item_type"; # should never happen } elsif($expected_value == $para->[1]{'number'}) { DEBUG > 1 and print STDERR " Numeric item has the expected value of $expected_value\n"; } else { DEBUG > 1 and print STDERR " Numeric item has ", $para->[1]{'number'}, " instead of the expected value of $expected_value\n"; $self->whine( $para->[1]{'start_line'}, "You have '=item " . $para->[1]{'number'} . "' instead of the expected '=item $expected_value'" ); $para->[1]{'number'} = $expected_value; # correcting!! } if(@$para == 2) { # For the cases where we /didn't/ push to @$para if($paras->[0][0] eq '~Para') { DEBUG and print STDERR "Assimilating following ~Para content into $over_type item\n"; push @$para, splice @{shift @$paras},2; } else { DEBUG and print STDERR "Can't assimilate following ", $paras->[0][0], "\n"; push @$para, ''; # Just so it's not contentless } } } elsif($over_type eq 'bullet') { my $item_type = $self->_get_item_type($para); # That kills the content of the item if it's a number or bullet. DEBUG and print STDERR " Item is of type ", $para->[0], " under $over_type\n"; if($item_type eq 'bullet') { # as expected! if( $para->[1]{'~_freaky_para_hack'} ) { DEBUG and print STDERR "Accomodating '=item * Foo' tolerance hack.\n"; push @$para, delete $para->[1]{'~_freaky_para_hack'}; } } elsif($item_type eq 'number') { $self->whine( $para->[1]{'start_line'}, "Expected '=item *'" ); push @$para, $para->[1]{'~orig_content'}; # and block assimilation of the next paragraph delete $para->[1]{'number'}; # Only a PROPER item-number element is allowed # to have a number attribute. } elsif($item_type eq 'text') { $self->whine( $para->[1]{'start_line'}, "Expected '=item *'" ); # But doesn't need processing. But it'll block assimilation # of the next para. } else { die "Unhandled item type $item_type"; # should never happen } if(@$para == 2) { # For the cases where we /didn't/ push to @$para if($paras->[0][0] eq '~Para') { DEBUG and print STDERR "Assimilating following ~Para content into $over_type item\n"; push @$para, splice @{shift @$paras},2; } else { DEBUG and print STDERR "Can't assimilate following ", $paras->[0][0], "\n"; push @$para, ''; # Just so it's not contentless } } } else { die "Unhandled =over type \"$over_type\"?"; # Shouldn't happen! } $para_type = 'Plain'; $para->[0] .= '-' . $over_type; # Whew. Now fall thru and process it. } elsif($para_type eq '=extend') { # Well, might as well implement it here. $self->_ponder_extend($para); next; # and skip } elsif($para_type eq '=encoding') { # Not actually acted on here, but we catch errors here. $self->_handle_encoding_second_level($para); next unless $self->keep_encoding_directive; $para_type = 'Plain'; } elsif($para_type eq '~Verbatim') { $para->[0] = 'Verbatim'; $para_type = '?Verbatim'; } elsif($para_type eq '~Para') { $para->[0] = 'Para'; $para_type = '?Plain'; } elsif($para_type eq 'Data') { $para->[0] = 'Data'; $para_type = '?Data'; } elsif( $para_type =~ s/^=//s and defined( $para_type = $self->{'accept_directives'}{$para_type} ) ) { DEBUG > 1 and print STDERR " Pondering known directive ${$para}[0] as $para_type\n"; } else { # An unknown directive! DEBUG > 1 and printf STDERR "Unhandled directive %s (Handled: %s)\n", $para->[0], join(' ', sort keys %{$self->{'accept_directives'}} ) ; $self->whine( $para->[1]{'start_line'}, "Unknown directive: $para->[0]" ); # And maybe treat it as text instead of just letting it go? next; } if($para_type =~ s/^\?//s) { if(! @$curr_open) { # usual case DEBUG and print STDERR "Treating $para_type paragraph as such because stack is empty.\n"; } else { my @fors = grep $_->[0] eq '=for', @$curr_open; DEBUG > 1 and print STDERR "Containing fors: ", join(',', map $_->[1]{'target'}, @fors), "\n"; if(! @fors) { DEBUG and print STDERR "Treating $para_type paragraph as such because stack has no =for's\n"; #} elsif(grep $_->[1]{'~resolve'}, @fors) { #} elsif(not grep !$_->[1]{'~resolve'}, @fors) { } elsif( $fors[-1][1]{'~resolve'} ) { # Look to the immediately containing for if($para_type eq 'Data') { DEBUG and print STDERR "Treating Data paragraph as Plain/Verbatim because the containing =for ($fors[-1][1]{'target'}) is a resolver\n"; $para->[0] = 'Para'; $para_type = 'Plain'; } else { DEBUG and print STDERR "Treating $para_type paragraph as such because the containing =for ($fors[-1][1]{'target'}) is a resolver\n"; } } else { DEBUG and print STDERR "Treating $para_type paragraph as Data because the containing =for ($fors[-1][1]{'target'}) is a non-resolver\n"; $para->[0] = $para_type = 'Data'; } } } #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ if($para_type eq 'Plain') { $self->_ponder_Plain($para); } elsif($para_type eq 'Verbatim') { $self->_ponder_Verbatim($para); } elsif($para_type eq 'Data') { $self->_ponder_Data($para); } else { die "\$para type is $para_type -- how did that happen?"; # Shouldn't happen. } #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ $para->[0] =~ s/^[~=]//s; DEBUG and print STDERR "\n", pretty($para), "\n"; # traverse the treelet (which might well be just one string scalar) $self->{'content_seen'} ||= 1; $self->_traverse_treelet_bit(@$para); } } return; } ########################################################################### # The sub-ponderers... sub _ponder_for { my ($self,$para,$curr_open,$paras) = @_; # Fake it out as a begin/end my $target; if(grep $_->[1]{'~ignore'}, @$curr_open) { DEBUG > 1 and print STDERR "Ignoring ignorable =for\n"; return 1; } for(my $i = 2; $i < @$para; ++$i) { if($para->[$i] =~ s/^\s*(\S+)\s*//s) { $target = $1; last; } } unless(defined $target) { $self->whine( $para->[1]{'start_line'}, "=for without a target?" ); return 1; } DEBUG > 1 and print STDERR "Faking out a =for $target as a =begin $target / =end $target\n"; $para->[0] = 'Data'; unshift @$paras, ['=begin', {'start_line' => $para->[1]{'start_line'}, '~really' => '=for'}, $target, ], $para, ['=end', {'start_line' => $para->[1]{'start_line'}, '~really' => '=for'}, $target, ], ; return 1; } sub _ponder_begin { my ($self,$para,$curr_open,$paras) = @_; my $content = join ' ', splice @$para, 2; $content =~ s/^\s+//s; $content =~ s/\s+$//s; unless(length($content)) { $self->whine( $para->[1]{'start_line'}, "=begin without a target?" ); DEBUG and print STDERR "Ignoring targetless =begin\n"; return 1; } my ($target, $title) = $content =~ m/^(\S+)\s*(.*)$/; $para->[1]{'title'} = $title if ($title); $para->[1]{'target'} = $target; # without any ':' $content = $target; # strip off the title $content =~ s/^:!/!:/s; my $neg; # whether this is a negation-match $neg = 1 if $content =~ s/^!//s; my $to_resolve; # whether to process formatting codes $to_resolve = 1 if $content =~ s/^://s; my $dont_ignore; # whether this target matches us foreach my $target_name ( split(',', $content, -1), $neg ? () : '*' ) { DEBUG > 2 and print STDERR " Considering whether =begin $content matches $target_name\n"; next unless $self->{'accept_targets'}{$target_name}; DEBUG > 2 and print STDERR " It DOES match the acceptable target $target_name!\n"; $to_resolve = 1 if $self->{'accept_targets'}{$target_name} eq 'force_resolve'; $dont_ignore = 1; $para->[1]{'target_matching'} = $target_name; last; # stop looking at other target names } if($neg) { if( $dont_ignore ) { $dont_ignore = ''; delete $para->[1]{'target_matching'}; DEBUG > 2 and print STDERR " But the leading ! means that this is a NON-match!\n"; } else { $dont_ignore = 1; $para->[1]{'target_matching'} = '!'; DEBUG > 2 and print STDERR " But the leading ! means that this IS a match!\n"; } } $para->[0] = '=for'; # Just what we happen to call these, internally $para->[1]{'~really'} ||= '=begin'; $para->[1]{'~ignore'} = (! $dont_ignore) || 0; $para->[1]{'~resolve'} = $to_resolve || 0; DEBUG > 1 and print STDERR " Making note to ", $dont_ignore ? 'not ' : '', "ignore contents of this region\n"; DEBUG > 1 and $dont_ignore and print STDERR " Making note to treat contents as ", ($to_resolve ? 'verbatim/plain' : 'data'), " paragraphs\n"; DEBUG > 1 and print STDERR " (Stack now: ", $self->_dump_curr_open(), ")\n"; push @$curr_open, $para; if(!$dont_ignore or scalar grep $_->[1]{'~ignore'}, @$curr_open) { DEBUG > 1 and print STDERR "Ignoring ignorable =begin\n"; } else { $self->{'content_seen'} ||= 1; $self->_handle_element_start((my $scratch='for'), $para->[1]); } return 1; } sub _ponder_end { my ($self,$para,$curr_open,$paras) = @_; my $content = join ' ', splice @$para, 2; $content =~ s/^\s+//s; $content =~ s/\s+$//s; DEBUG and print STDERR "Ogling '=end $content' directive\n"; unless(length($content)) { $self->whine( $para->[1]{'start_line'}, "'=end' without a target?" . ( ( @$curr_open and $curr_open->[-1][0] eq '=for' ) ? ( " (Should be \"=end " . $curr_open->[-1][1]{'target'} . '")' ) : '' ) ); DEBUG and print STDERR "Ignoring targetless =end\n"; return 1; } unless($content =~ m/^\S+$/) { # i.e., unless it's one word $self->whine( $para->[1]{'start_line'}, "'=end $content' is invalid. (Stack: " . $self->_dump_curr_open() . ')' ); DEBUG and print STDERR "Ignoring mistargetted =end $content\n"; return 1; } unless(@$curr_open and $curr_open->[-1][0] eq '=for') { $self->whine( $para->[1]{'start_line'}, "=end $content without matching =begin. (Stack: " . $self->_dump_curr_open() . ')' ); DEBUG and print STDERR "Ignoring mistargetted =end $content\n"; return 1; } unless($content eq $curr_open->[-1][1]{'target'}) { $self->whine( $para->[1]{'start_line'}, "=end $content doesn't match =begin " . $curr_open->[-1][1]{'target'} . ". (Stack: " . $self->_dump_curr_open() . ')' ); DEBUG and print STDERR "Ignoring mistargetted =end $content at line $para->[1]{'start_line'}\n"; return 1; } # Else it's okay to close... if(grep $_->[1]{'~ignore'}, @$curr_open) { DEBUG > 1 and print STDERR "Not firing any event for this =end $content because in an ignored region\n"; # And that may be because of this to-be-closed =for region, or some # other one, but it doesn't matter. } else { $curr_open->[-1][1]{'start_line'} = $para->[1]{'start_line'}; # what's that for? $self->{'content_seen'} ||= 1; $self->_handle_element_end( my $scratch = 'for', $para->[1]); } DEBUG > 1 and print STDERR "Popping $curr_open->[-1][0] $curr_open->[-1][1]{'target'} because of =end $content\n"; pop @$curr_open; return 1; } sub _ponder_doc_end { my ($self,$para,$curr_open,$paras) = @_; if(@$curr_open) { # Deal with things left open DEBUG and print STDERR "Stack is nonempty at end-document: (", $self->_dump_curr_open(), ")\n"; DEBUG > 9 and print STDERR "Stack: ", pretty($curr_open), "\n"; unshift @$paras, $self->_closers_for_all_curr_open; # Make sure there is exactly one ~end in the parastack, at the end: @$paras = grep $_->[0] ne '~end', @$paras; push @$paras, $para, $para; # We need two -- once for the next cycle where we # generate errata, and then another to be at the end # when that loop back around to process the errata. return 1; } else { DEBUG and print STDERR "Okay, stack is empty now.\n"; } # Try generating errata section, if applicable unless($self->{'~tried_gen_errata'}) { $self->{'~tried_gen_errata'} = 1; my @extras = $self->_gen_errata(); if(@extras) { unshift @$paras, @extras; DEBUG and print STDERR "Generated errata... relooping...\n"; return 1; # I.e., loop around again to process these fake-o paragraphs } } splice @$paras; # Well, that's that for this paragraph buffer. DEBUG and print STDERR "Throwing end-document event.\n"; $self->_handle_element_end( my $scratch = 'Document' ); return 1; # Hasta la byebye } sub _ponder_pod { my ($self,$para,$curr_open,$paras) = @_; $self->whine( $para->[1]{'start_line'}, "=pod directives shouldn't be over one line long! Ignoring all " . (@$para - 2) . " lines of content" ) if @$para > 3; # Content ignored unless 'pod_handler' is set if (my $pod_handler = $self->{'pod_handler'}) { my ($line_num, $line) = map $_, $para->[1]{'start_line'}, $para->[2]; $line = $line eq '' ? "=pod" : "=pod $line"; # imitate cut_handler output $pod_handler->($line, $line_num, $self); } # The surrounding methods set content_seen, so let us remain consistent. # I do not know why it was not here before -- should it not be here? # $self->{'content_seen'} ||= 1; return; } sub _ponder_over { my ($self,$para,$curr_open,$paras) = @_; return 1 unless @$paras; my $list_type; if($paras->[0][0] eq '=item') { # most common case $list_type = $self->_get_initial_item_type($paras->[0]); } elsif($paras->[0][0] eq '=back') { # Ignore empty lists by default if ($self->{'parse_empty_lists'}) { $list_type = 'empty'; } else { shift @$paras; return 1; } } elsif($paras->[0][0] eq '~end') { $self->whine( $para->[1]{'start_line'}, "=over is the last thing in the document?!" ); return 1; # But feh, ignore it. } else { $list_type = 'block'; } $para->[1]{'~type'} = $list_type; push @$curr_open, $para; # yes, we reuse the paragraph as a stack item my $content = join ' ', splice @$para, 2; my $overness; if($content =~ m/^\s*$/s) { $para->[1]{'indent'} = 4; } elsif($content =~ m/^\s*((?:\d*\.)?\d+)\s*$/s) { no integer; $para->[1]{'indent'} = $1; if($1 == 0) { $self->whine( $para->[1]{'start_line'}, "Can't have a 0 in =over $content" ); $para->[1]{'indent'} = 4; } } else { $self->whine( $para->[1]{'start_line'}, "=over should be: '=over' or '=over positive_number'" ); $para->[1]{'indent'} = 4; } DEBUG > 1 and print STDERR "=over found of type $list_type\n"; $self->{'content_seen'} ||= 1; $self->_handle_element_start((my $scratch = 'over-' . $list_type), $para->[1]); return; } sub _ponder_back { my ($self,$para,$curr_open,$paras) = @_; # TODO: fire off or or ?? my $content = join ' ', splice @$para, 2; if($content =~ m/\S/) { $self->whine( $para->[1]{'start_line'}, "=back doesn't take any parameters, but you said =back $content" ); } if(@$curr_open and $curr_open->[-1][0] eq '=over') { DEBUG > 1 and print STDERR "=back happily closes matching =over\n"; # Expected case: we're closing the most recently opened thing #my $over = pop @$curr_open; $self->{'content_seen'} ||= 1; $self->_handle_element_end( my $scratch = 'over-' . ( (pop @$curr_open)->[1]{'~type'} ), $para->[1] ); } else { DEBUG > 1 and print STDERR "=back found without a matching =over. Stack: (", join(', ', map $_->[0], @$curr_open), ").\n"; $self->whine( $para->[1]{'start_line'}, '=back without =over' ); return 1; # and ignore it } } sub _ponder_item { my ($self,$para,$curr_open,$paras) = @_; my $over; unless(@$curr_open and $over = (grep { $_->[0] eq '=over' } @$curr_open)[-1]) { $self->whine( $para->[1]{'start_line'}, "'=item' outside of any '=over'" ); unshift @$paras, ['=over', {'start_line' => $para->[1]{'start_line'}}, ''], $para ; return 1; } my $over_type = $over->[1]{'~type'}; if(!$over_type) { # Shouldn't happen1 die "Typeless over in stack, starting at line " . $over->[1]{'start_line'}; } elsif($over_type eq 'block') { unless($curr_open->[-1][1]{'~bitched_about'}) { $curr_open->[-1][1]{'~bitched_about'} = 1; $self->whine( $curr_open->[-1][1]{'start_line'}, "You can't have =items (as at line " . $para->[1]{'start_line'} . ") unless the first thing after the =over is an =item" ); } # Just turn it into a paragraph and reconsider it $para->[0] = '~Para'; unshift @$paras, $para; return 1; } elsif($over_type eq 'text') { my $item_type = $self->_get_item_type($para); # That kills the content of the item if it's a number or bullet. DEBUG and print STDERR " Item is of type ", $para->[0], " under $over_type\n"; if($item_type eq 'text') { # Nothing special needs doing for 'text' } elsif($item_type eq 'number' or $item_type eq 'bullet') { $self->whine( $para->[1]{'start_line'}, "Expected text after =item, not a $item_type" ); # Undo our clobbering: push @$para, $para->[1]{'~orig_content'}; delete $para->[1]{'number'}; # Only a PROPER item-number element is allowed # to have a number attribute. } else { die "Unhandled item type $item_type"; # should never happen } # =item-text thingies don't need any assimilation, it seems. } elsif($over_type eq 'number') { my $item_type = $self->_get_item_type($para); # That kills the content of the item if it's a number or bullet. DEBUG and print STDERR " Item is of type ", $para->[0], " under $over_type\n"; my $expected_value = ++ $curr_open->[-1][1]{'~counter'}; if($item_type eq 'bullet') { # Hm, it's not numeric. Correct for this. $para->[1]{'number'} = $expected_value; $self->whine( $para->[1]{'start_line'}, "Expected '=item $expected_value'" ); push @$para, $para->[1]{'~orig_content'}; # restore the bullet, blocking the assimilation of next para } elsif($item_type eq 'text') { # Hm, it's not numeric. Correct for this. $para->[1]{'number'} = $expected_value; $self->whine( $para->[1]{'start_line'}, "Expected '=item $expected_value'" ); # Text content will still be there and will block next ~Para } elsif($item_type ne 'number') { die "Unknown item type $item_type"; # should never happen } elsif($expected_value == $para->[1]{'number'}) { DEBUG > 1 and print STDERR " Numeric item has the expected value of $expected_value\n"; } else { DEBUG > 1 and print STDERR " Numeric item has ", $para->[1]{'number'}, " instead of the expected value of $expected_value\n"; $self->whine( $para->[1]{'start_line'}, "You have '=item " . $para->[1]{'number'} . "' instead of the expected '=item $expected_value'" ); $para->[1]{'number'} = $expected_value; # correcting!! } if(@$para == 2) { # For the cases where we /didn't/ push to @$para if($paras->[0][0] eq '~Para') { DEBUG and print STDERR "Assimilating following ~Para content into $over_type item\n"; push @$para, splice @{shift @$paras},2; } else { DEBUG and print STDERR "Can't assimilate following ", $paras->[0][0], "\n"; push @$para, ''; # Just so it's not contentless } } } elsif($over_type eq 'bullet') { my $item_type = $self->_get_item_type($para); # That kills the content of the item if it's a number or bullet. DEBUG and print STDERR " Item is of type ", $para->[0], " under $over_type\n"; if($item_type eq 'bullet') { # as expected! if( $para->[1]{'~_freaky_para_hack'} ) { DEBUG and print STDERR "Accomodating '=item * Foo' tolerance hack.\n"; push @$para, delete $para->[1]{'~_freaky_para_hack'}; } } elsif($item_type eq 'number') { $self->whine( $para->[1]{'start_line'}, "Expected '=item *'" ); push @$para, $para->[1]{'~orig_content'}; # and block assimilation of the next paragraph delete $para->[1]{'number'}; # Only a PROPER item-number element is allowed # to have a number attribute. } elsif($item_type eq 'text') { $self->whine( $para->[1]{'start_line'}, "Expected '=item *'" ); # But doesn't need processing. But it'll block assimilation # of the next para. } else { die "Unhandled item type $item_type"; # should never happen } if(@$para == 2) { # For the cases where we /didn't/ push to @$para if($paras->[0][0] eq '~Para') { DEBUG and print STDERR "Assimilating following ~Para content into $over_type item\n"; push @$para, splice @{shift @$paras},2; } else { DEBUG and print STDERR "Can't assimilate following ", $paras->[0][0], "\n"; push @$para, ''; # Just so it's not contentless } } } else { die "Unhandled =over type \"$over_type\"?"; # Shouldn't happen! } $para->[0] .= '-' . $over_type; return; } sub _ponder_Plain { my ($self,$para) = @_; DEBUG and print STDERR " giving plain treatment...\n"; unless( @$para == 2 or ( @$para == 3 and $para->[2] eq '' ) or $para->[1]{'~cooked'} ) { push @$para, @{$self->_make_treelet( join("\n", splice(@$para, 2)), $para->[1]{'start_line'} )}; } # Empty paragraphs don't need a treelet for any reason I can see. # And precooked paragraphs already have a treelet. return; } sub _ponder_Verbatim { my ($self,$para) = @_; DEBUG and print STDERR " giving verbatim treatment...\n"; $para->[1]{'xml:space'} = 'preserve'; my $indent = $self->strip_verbatim_indent; if ($indent && ref $indent eq 'CODE') { my @shifted = (shift @{$para}, shift @{$para}); $indent = $indent->($para); unshift @{$para}, @shifted; } for(my $i = 2; $i < @$para; $i++) { foreach my $line ($para->[$i]) { # just for aliasing # Strip indentation. $line =~ s/^\Q$indent// if $indent && !($self->{accept_codes} && $self->{accept_codes}{VerbatimFormatted}); while( $line =~ # Sort of adapted from Text::Tabs -- yes, it's hardwired in that # tabs are at every EIGHTH column. For portability, it has to be # one setting everywhere, and 8th wins. s/^([^\t]*)(\t+)/$1.(" " x ((length($2)<<3)-(length($1)&7)))/e ) {} # TODO: whinge about (or otherwise treat) unindented or overlong lines } } # Now the VerbatimFormatted hoodoo... if( $self->{'accept_codes'} and $self->{'accept_codes'}{'VerbatimFormatted'} ) { while(@$para > 3 and $para->[-1] !~ m/\S/) { pop @$para } # Kill any number of terminal newlines $self->_verbatim_format($para); } elsif ($self->{'codes_in_verbatim'}) { push @$para, @{$self->_make_treelet( join("\n", splice(@$para, 2)), $para->[1]{'start_line'}, $para->[1]{'xml:space'} )}; $para->[-1] =~ s/\n+$//s; # Kill any number of terminal newlines } else { push @$para, join "\n", splice(@$para, 2) if @$para > 3; $para->[-1] =~ s/\n+$//s; # Kill any number of terminal newlines } return; } sub _ponder_Data { my ($self,$para) = @_; DEBUG and print STDERR " giving data treatment...\n"; $para->[1]{'xml:space'} = 'preserve'; push @$para, join "\n", splice(@$para, 2) if @$para > 3; return; } ########################################################################### sub _traverse_treelet_bit { # for use only by the routine above my($self, $name) = splice @_,0,2; my $scratch; $self->_handle_element_start(($scratch=$name), shift @_); while (@_) { my $x = shift; if (ref($x)) { &_traverse_treelet_bit($self, @$x); } else { $x .= shift while @_ && !ref($_[0]); $self->_handle_text($x); } } $self->_handle_element_end($scratch=$name); return; } #@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ sub _closers_for_all_curr_open { my $self = $_[0]; my @closers; foreach my $still_open (@{ $self->{'curr_open'} || return }) { my @copy = @$still_open; $copy[1] = {%{ $copy[1] }}; #$copy[1]{'start_line'} = -1; if($copy[0] eq '=for') { $copy[0] = '=end'; } elsif($copy[0] eq '=over') { $self->whine( $still_open->[1]{start_line} , "=over without closing =back" ); $copy[0] = '=back'; } else { die "I don't know how to auto-close an open $copy[0] region"; } unless( @copy > 2 ) { push @copy, $copy[1]{'target'}; $copy[-1] = '' unless defined $copy[-1]; # since =over's don't have targets } $copy[1]{'fake-closer'} = 1; DEBUG and print STDERR "Queuing up fake-o event: ", pretty(\@copy), "\n"; unshift @closers, \@copy; } return @closers; } #-------------------------------------------------------------------------- sub _verbatim_format { my($it, $p) = @_; my $formatting; for(my $i = 2; $i < @$p; $i++) { # work backwards over the lines DEBUG and print STDERR "_verbatim_format appends a newline to $i: $p->[$i]\n"; $p->[$i] .= "\n"; # Unlike with simple Verbatim blocks, we don't end up just doing # a join("\n", ...) on the contents, so we have to append a # newline to ever line, and then nix the last one later. } if( DEBUG > 4 ) { print STDERR "<<\n"; for(my $i = $#$p; $i >= 2; $i--) { # work backwards over the lines print STDERR "_verbatim_format $i: $p->[$i]"; } print STDERR ">>\n"; } for(my $i = $#$p; $i > 2; $i--) { # work backwards over the lines, except the first (#2) #next unless $p->[$i] =~ m{^#:([ \^\/\%]*)\n?$}s # and $p->[$i-1] !~ m{^#:[ \^\/\%]*\n?$}s; # look at a formatty line preceding a nonformatty one DEBUG > 5 and print STDERR "Scrutinizing line $i: $$p[$i]\n"; if($p->[$i] =~ m{^#:([ \^\/\%]*)\n?$}s) { DEBUG > 5 and print STDERR " It's a formatty line. ", "Peeking at previous line ", $i-1, ": $$p[$i-1]: \n"; if( $p->[$i-1] =~ m{^#:[ \^\/\%]*\n?$}s ) { DEBUG > 5 and print STDERR " Previous line is formatty! Skipping this one.\n"; next; } else { DEBUG > 5 and print STDERR " Previous line is non-formatty! Yay!\n"; } } else { DEBUG > 5 and print STDERR " It's not a formatty line. Ignoring\n"; next; } # A formatty line has to have #: in the first two columns, and uses # "^" to mean bold, "/" to mean underline, and "%" to mean bold italic. # Example: # What do you want? i like pie. [or whatever] # #:^^^^^^^^^^^^^^^^^ ///////////// DEBUG > 4 and print STDERR "_verbatim_format considers:\n<$p->[$i-1]>\n<$p->[$i]>\n"; $formatting = ' ' . $1; $formatting =~ s/\s+$//s; # nix trailing whitespace unless(length $formatting and $p->[$i-1] =~ m/\S/) { # no-op splice @$p,$i,1; # remove this line $i--; # don't consider next line next; } if( length($formatting) >= length($p->[$i-1]) ) { $formatting = substr($formatting, 0, length($p->[$i-1]) - 1) . ' '; } else { $formatting .= ' ' x (length($p->[$i-1]) - length($formatting)); } # Make $formatting and the previous line be exactly the same length, # with $formatting having a " " as the last character. DEBUG > 4 and print STDERR "Formatting <$formatting> on <", $p->[$i-1], ">\n"; my @new_line; while( $formatting =~ m{\G(( +)|(\^+)|(\/+)|(\%+))}g ) { #print STDERR "Format matches $1\n"; if($2) { #print STDERR "SKIPPING <$2>\n"; push @new_line, substr($p->[$i-1], pos($formatting)-length($1), length($1)); } else { #print STDERR "SNARING $+\n"; push @new_line, [ ( $3 ? 'VerbatimB' : $4 ? 'VerbatimI' : $5 ? 'VerbatimBI' : die("Should never get called") ), {}, substr($p->[$i-1], pos($formatting)-length($1), length($1)) ]; #print STDERR "Formatting <$new_line[-1][-1]> as $new_line[-1][0]\n"; } } my @nixed = splice @$p, $i-1, 2, @new_line; # replace myself and the next line DEBUG > 10 and print STDERR "Nixed count: ", scalar(@nixed), "\n"; DEBUG > 6 and print STDERR "New version of the above line is these tokens (", scalar(@new_line), "):", map( ref($_)?"<@$_> ":"<$_>", @new_line ), "\n"; $i--; # So the next line we scrutinize is the line before the one # that we just went and formatted } $p->[0] = 'VerbatimFormatted'; # Collapse adjacent text nodes, just for kicks. for( my $i = 2; $i > $#$p; $i++ ) { # work forwards over the tokens except for the last if( !ref($p->[$i]) and !ref($p->[$i + 1]) ) { DEBUG > 5 and print STDERR "_verbatim_format merges {$p->[$i]} and {$p->[$i+1]}\n"; $p->[$i] .= splice @$p, $i+1, 1; # merge --$i; # and back up } } # Now look for the last text token, and remove the terminal newline for( my $i = $#$p; $i >= 2; $i-- ) { # work backwards over the tokens, even the first if( !ref($p->[$i]) ) { if($p->[$i] =~ s/\n$//s) { DEBUG > 5 and print STDERR "_verbatim_format killed the terminal newline on #$i: {$p->[$i]}, after {$p->[$i-1]}\n"; } else { DEBUG > 5 and print STDERR "No terminal newline on #$i: {$p->[$i]}, after {$p->[$i-1]} !?\n"; } last; # we only want the next one } } return; } #@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ sub _treelet_from_formatting_codes { # Given a paragraph, returns a treelet. Full of scary tokenizing code. # Like [ '~Top', {'start_line' => $start_line}, # "I like ", # [ 'B', {}, "pie" ], # "!" # ] my($self, $para, $start_line, $preserve_space) = @_; my $treelet = ['~Top', {'start_line' => $start_line},]; unless ($preserve_space || $self->{'preserve_whitespace'}) { $para =~ s/\s+/ /g; # collapse and trim all whitespace first. $para =~ s/ $//; $para =~ s/^ //; } # Only apparent problem the above code is that N<< >> turns into # N<< >>. But then, word wrapping does that too! So don't do that! my @stack; my @lineage = ($treelet); my $raw = ''; # raw content of L<> fcode before splitting/processing # XXX 'raw' is not 100% accurate: all surrounding whitespace is condensed # into just 1 ' '. Is this the regex's doing or 'raw's? my $inL = 0; DEBUG > 4 and print STDERR "Paragraph:\n$para\n\n"; # Here begins our frightening tokenizer RE. The following regex matches # text in four main parts: # # * Start-codes. The first alternative matches C< or C<<, the latter # followed by some whitespace. $1 will hold the entire start code # (including any space following a multiple-angle-bracket delimiter), # and $2 will hold only the additional brackets past the first in a # multiple-bracket delimiter. length($2) + 1 will be the number of # closing brackets we have to find. # # * Closing brackets. Match some amount of whitespace followed by # multiple close brackets. The logic to see if this closes anything # is down below. Note that in order to parse C<< >> correctly, we # have to use look-behind (?<=\s\s), since the match of the starting # code will have consumed the whitespace. # # * A single closing bracket, to close a simple code like C<>. # # * Something that isn't a start or end code. We have to be careful # about accepting whitespace, since perlpodspec says that any whitespace # before a multiple-bracket closing delimiter should be ignored. # while($para =~ m/\G (?: # Match starting codes, including the whitespace following a # multiple-delimiter start code. $1 gets the whole start code and # $2 gets all but one of the {2,}) | (\s?>) # $5: simple end-codes | ( # $6: stuff containing no start-codes or end-codes (?: [^A-Z\s>] | (?: [A-Z](?!<) ) | # whitespace is ok, but we don't want to eat the whitespace before # a multiple-bracket end code. # NOTE: we may still have problems with e.g. S<< >> (?: \s(?!\s*>{2,}) ) )+ ) ) /xgo ) { DEBUG > 4 and print STDERR "\nParagraphic tokenstack = (@stack)\n"; if(defined $1) { if(defined $2) { DEBUG > 3 and print STDERR "Found complex start-text code \"$1\"\n"; push @stack, length($2) + 1; # length of the necessary complex end-code string } else { DEBUG > 3 and print STDERR "Found simple start-text code \"$1\"\n"; push @stack, 0; # signal that we're looking for simple } push @lineage, [ substr($1,0,1), {}, ]; # new node object push @{ $lineage[-2] }, $lineage[-1]; if ('L' eq substr($1,0,1)) { $raw = $inL ? $raw.$1 : ''; # reset raw content accumulator $inL = 1; } else { $raw .= $1 if $inL; } } elsif(defined $4) { DEBUG > 3 and print STDERR "Found apparent complex end-text code \"$3$4\"\n"; # This is where it gets messy... if(! @stack) { # We saw " >>>>" but needed nothing. This is ALL just stuff then. DEBUG > 4 and print STDERR " But it's really just stuff.\n"; push @{ $lineage[-1] }, $3, $4; next; } elsif(!$stack[-1]) { # We saw " >>>>" but needed only ">". Back pos up. DEBUG > 4 and print STDERR " And that's more than we needed to close simple.\n"; push @{ $lineage[-1] }, $3; # That was a for-real space, too. pos($para) = pos($para) - length($4) + 1; } elsif($stack[-1] == length($4)) { # We found " >>>>", and it was exactly what we needed. Commonest case. DEBUG > 4 and print STDERR " And that's exactly what we needed to close complex.\n"; } elsif($stack[-1] < length($4)) { # We saw " >>>>" but needed only " >>". Back pos up. DEBUG > 4 and print STDERR " And that's more than we needed to close complex.\n"; pos($para) = pos($para) - length($4) + $stack[-1]; } else { # We saw " >>>>" but needed " >>>>>>". So this is all just stuff! DEBUG > 4 and print STDERR " But it's really just stuff, because we needed more.\n"; push @{ $lineage[-1] }, $3, $4; next; } #print STDERR "\nHOOBOY ", scalar(@{$lineage[-1]}), "!!!\n"; push @{ $lineage[-1] }, '' if 2 == @{ $lineage[-1] }; # Keep the element from being childless pop @stack; pop @lineage; unless (@stack) { # not in an L if there are no open fcodes $inL = 0; if (ref $lineage[-1][-1] && $lineage[-1][-1][0] eq 'L') { $lineage[-1][-1][1]{'raw'} = $raw } } $raw .= $3.$4 if $inL; } elsif(defined $5) { DEBUG > 3 and print STDERR "Found apparent simple end-text code \"$5\"\n"; if(@stack and ! $stack[-1]) { # We're indeed expecting a simple end-code DEBUG > 4 and print STDERR " It's indeed an end-code.\n"; if(length($5) == 2) { # There was a space there: " >" push @{ $lineage[-1] }, ' '; } elsif( 2 == @{ $lineage[-1] } ) { # Closing a childless element push @{ $lineage[-1] }, ''; # keep it from being really childless } pop @stack; pop @lineage; } else { DEBUG > 4 and print STDERR " It's just stuff.\n"; push @{ $lineage[-1] }, $5; } unless (@stack) { # not in an L if there are no open fcodes $inL = 0; if (ref $lineage[-1][-1] && $lineage[-1][-1][0] eq 'L') { $lineage[-1][-1][1]{'raw'} = $raw } } $raw .= $5 if $inL; } elsif(defined $6) { DEBUG > 3 and print STDERR "Found stuff \"$6\"\n"; push @{ $lineage[-1] }, $6; $raw .= $6 if $inL; # XXX does not capture multiplace whitespaces -- 'raw' ends up with # at most 1 leading/trailing whitespace, why not all of it? } else { # should never ever ever ever happen DEBUG and print STDERR "AYYAYAAAAA at line ", __LINE__, "\n"; die "SPORK 512512!"; } } if(@stack) { # Uhoh, some sequences weren't closed. my $x= "..."; while(@stack) { push @{ $lineage[-1] }, '' if 2 == @{ $lineage[-1] }; # Hmmmmm! my $code = (pop @lineage)->[0]; my $ender_length = pop @stack; if($ender_length) { --$ender_length; $x = $code . ("<" x $ender_length) . " $x " . (">" x $ender_length); } else { $x = $code . "<$x>"; } } DEBUG > 1 and print STDERR "Unterminated $x sequence\n"; $self->whine($start_line, "Unterminated $x sequence", ); } return $treelet; } #@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ sub text_content_of_treelet { # method: $parser->text_content_of_treelet($lol) return stringify_lol($_[1]); } sub stringify_lol { # function: stringify_lol($lol) my $string_form = ''; _stringify_lol( $_[0] => \$string_form ); return $string_form; } sub _stringify_lol { # the real recursor my($lol, $to) = @_; for(my $i = 2; $i < @$lol; ++$i) { if( ref($lol->[$i] || '') and UNIVERSAL::isa($lol->[$i], 'ARRAY') ) { _stringify_lol( $lol->[$i], $to); # recurse! } else { $$to .= $lol->[$i]; } } return; } #@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ sub _dump_curr_open { # return a string representation of the stack my $curr_open = $_[0]{'curr_open'}; return '[empty]' unless @$curr_open; return join '; ', map {; ($_->[0] eq '=for') ? ( ($_->[1]{'~really'} || '=over') . ' ' . $_->[1]{'target'}) : $_->[0] } @$curr_open ; } ########################################################################### my %pretty_form = ( "\a" => '\a', # ding! "\b" => '\b', # BS "\e" => '\e', # ESC "\f" => '\f', # FF "\t" => '\t', # tab "\cm" => '\cm', "\cj" => '\cj', "\n" => '\n', # probably overrides one of either \cm or \cj '"' => '\"', '\\' => '\\\\', '$' => '\\$', '@' => '\\@', '%' => '\\%', '#' => '\\#', ); sub pretty { # adopted from Class::Classless # Not the most brilliant routine, but passable. # Don't give it a cyclic data structure! my @stuff = @_; # copy my $x; my $out = # join ",\n" . join ", ", map {; if(!defined($_)) { "undef"; } elsif(ref($_) eq 'ARRAY' or ref($_) eq 'Pod::Simple::LinkSection') { $x = "[ " . pretty(@$_) . " ]" ; $x; } elsif(ref($_) eq 'SCALAR') { $x = "\\" . pretty($$_) ; $x; } elsif(ref($_) eq 'HASH') { my $hr = $_; $x = "{" . join(", ", map(pretty($_) . '=>' . pretty($hr->{$_}), sort keys %$hr ) ) . "}" ; $x; } elsif(!length($_)) { q{''} # empty string } elsif( $_ eq '0' # very common case or( m/^-?(?:[123456789]\d*|0)(?:\.\d+)?$/s and $_ ne '-0' # the strange case that RE lets thru ) ) { $_; } else { # Yes, explicitly name every character desired. There are shorcuts one # could make, but I (Karl Williamson) was afraid that some Perl # releases would have bugs in some of them. For example [A-Z] works # even on EBCDIC platforms to match exactly the 26 uppercase English # letters, but I don't know if it has always worked without bugs. It # seemed safest just to list the characters. # s<([^\x20\x21\x23\x27-\x3F\x41-\x5B\x5D-\x7E])> s<([^ !#'()*+,\-./0123456789:;\<=\>?ABCDEFGHIJKLMNOPQRSTUVWXYZ\[\]^_`abcdefghijklmnopqrstuvwxyz{|}~])> <$pretty_form{$1} || '\\x{'.sprintf("%x", ord($1)).'}'>eg; #<$pretty_form{$1} || '\\x'.(unpack("H2",$1))>eg; qq{"$_"}; } } @stuff; # $out =~ s/\n */ /g if length($out) < 75; return $out; } #@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ # A rather unsubtle method of blowing away all the state information # from a parser object so it can be reused. Provided as a utility for # backward compatibility in Pod::Man, etc. but not recommended for # general use. sub reinit { my $self = shift; foreach (qw(source_dead source_filename doc_has_started start_of_pod_block content_seen last_was_blank paras curr_open line_count pod_para_count in_pod ~tried_gen_errata all_errata errata errors_seen Title)) { delete $self->{$_}; } } #@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ 1; require 5; package Pod::Simple::DumpAsText; $VERSION = '3.35'; use Pod::Simple (); BEGIN {@ISA = ('Pod::Simple')} use strict; use Carp (); BEGIN { *DEBUG = \&Pod::Simple::DEBUG unless defined &DEBUG } sub new { my $self = shift; my $new = $self->SUPER::new(@_); $new->{'output_fh'} ||= *STDOUT{IO}; $new->accept_codes('VerbatimFormatted'); $new->keep_encoding_directive(1); return $new; } #@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ sub _handle_element_start { # ($self, $element_name, $attr_hash_r) my $fh = $_[0]{'output_fh'}; my($key, $value); DEBUG and print STDERR "++ $_[1]\n"; print $fh ' ' x ($_[0]{'indent'} || 0), "++", $_[1], "\n"; $_[0]{'indent'}++; while(($key,$value) = each %{$_[2]}) { unless($key =~ m/^~/s) { next if $key eq 'start_line' and $_[0]{'hide_line_numbers'}; _perly_escape($key); _perly_escape($value); printf $fh qq{%s \\ "%s" => "%s"\n}, ' ' x ($_[0]{'indent'} || 0), $key, $value; } } return; } sub _handle_text { DEBUG and print STDERR "== \"$_[1]\"\n"; if(length $_[1]) { my $indent = ' ' x $_[0]{'indent'}; my $text = $_[1]; _perly_escape($text); $text =~ # A not-totally-brilliant wrapping algorithm: s/( [^\n]{55} # Snare some characters from a line [^\n\ ]{0,50} # and finish any current word ) \ {1,10}(?!\n) # capture some spaces not at line-end /$1"\n$indent . "/gx # => line-break here ; print {$_[0]{'output_fh'}} $indent, '* "', $text, "\"\n"; } return; } sub _handle_element_end { DEBUG and print STDERR "-- $_[1]\n"; print {$_[0]{'output_fh'}} ' ' x --$_[0]{'indent'}, "--", $_[1], "\n"; return; } # . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . sub _perly_escape { foreach my $x (@_) { $x =~ s/([^\x00-\xFF])/sprintf'\x{%X}',ord($1)/eg; # Escape things very cautiously: $x =~ s/([^-\n\t \&\<\>\'!\#\%\(\)\*\+,\.\/\:\;=\?\~\[\]\^_\`\{\|\}abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789])/sprintf'\x%02X',ord($1)/eg; } return; } #@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ 1; __END__ require 5; package Pod::Simple::Methody; use strict; use Pod::Simple (); use vars qw(@ISA $VERSION); $VERSION = '3.35'; @ISA = ('Pod::Simple'); # Yes, we could use named variables, but I want this to be impose # as little an additional performance hit as possible. sub _handle_element_start { $_[1] =~ tr/-:./__/; ( $_[0]->can( 'start_' . $_[1] ) || return )->( $_[0], $_[2] ); } sub _handle_text { ( $_[0]->can( 'handle_text' ) || return )->( @_ ); } sub _handle_element_end { $_[1] =~ tr/-:./__/; ( $_[0]->can( 'end_' . $_[1] ) || return )->( $_[0], $_[2] ); } 1; __END__ package Pod::Simple::XHTML; use strict; use vars qw( $VERSION @ISA $HAS_HTML_ENTITIES ); $VERSION = '3.35'; use Pod::Simple::Methody (); @ISA = ('Pod::Simple::Methody'); BEGIN { $HAS_HTML_ENTITIES = eval "require HTML::Entities; 1"; } my %entities = ( q{>} => 'gt', q{<} => 'lt', q{'} => '#39', q{"} => 'quot', q{&} => 'amp', ); sub encode_entities { my $self = shift; my $ents = $self->html_encode_chars; return HTML::Entities::encode_entities( $_[0], $ents ) if $HAS_HTML_ENTITIES; if (defined $ents) { $ents =~ s,(?_accessorize( 'perldoc_url_prefix', 'perldoc_url_postfix', 'man_url_prefix', 'man_url_postfix', 'title_prefix', 'title_postfix', 'html_css', 'html_javascript', 'html_doctype', 'html_charset', 'html_encode_chars', 'html_h_level', 'title', # Used internally for the title extracted from the content 'default_title', 'force_title', 'html_header', 'html_footer', 'index', 'anchor_items', 'backlink', 'batch_mode', # whether we're in batch mode 'batch_mode_current_level', # When in batch mode, how deep the current module is: 1 for "LWP", # 2 for "LWP::Procotol", 3 for "LWP::Protocol::GHTTP", etc ); #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ sub new { my $self = shift; my $new = $self->SUPER::new(@_); $new->{'output_fh'} ||= *STDOUT{IO}; $new->perldoc_url_prefix('http://search.cpan.org/perldoc?'); $new->man_url_prefix('http://man.he.net/man'); $new->html_charset('ISO-8859-1'); $new->nix_X_codes(1); $new->{'scratch'} = ''; $new->{'to_index'} = []; $new->{'output'} = []; $new->{'saved'} = []; $new->{'ids'} = { '_podtop_' => 1 }; # used in $new->{'in_li'} = []; $new->{'__region_targets'} = []; $new->{'__literal_targets'} = {}; $new->accept_targets_as_html( 'html', 'HTML' ); return $new; } sub html_header_tags { my $self = shift; return $self->{html_header_tags} = shift if @_; return $self->{html_header_tags} ||= ''; } #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ sub __in_literal_xhtml_region { return unless @{ $_[0]{__region_targets} }; my $target = $_[0]{__region_targets}[-1]; return $_[0]{__literal_targets}{ $target }; } sub accept_targets_as_html { my ($self, @targets) = @_; $self->accept_targets(@targets); $self->{__literal_targets}{$_} = 1 for @targets; } sub handle_text { # escape special characters in HTML (<, >, &, etc) my $text = $_[0]->__in_literal_xhtml_region ? $_[1] : $_[0]->encode_entities( $_[1] ); if ($_[0]{'in_code'} && @{$_[0]{'in_code'}}) { # Intentionally use the raw text in $_[1], even if we're not in a # literal xhtml region, since handle_code calls encode_entities. $_[0]->handle_code( $_[1], $_[0]{'in_code'}[-1] ); } else { if ($_[0]->{in_for}) { my $newlines = $_[0]->__in_literal_xhtml_region ? "\n\n" : ''; if ($_[0]->{started_for}) { if ($text =~ /\S/) { delete $_[0]->{started_for}; $_[0]{'scratch'} .= $text . $newlines; } # Otherwise, append nothing until we have something to append. } else { # The parser sometimes preserves newlines and sometimes doesn't! $text =~ s/\n\z//; $_[0]{'scratch'} .= $text . $newlines; } } else { # Just plain text. $_[0]{'scratch'} .= $text; } } $_[0]{htext} .= $text if $_[0]{'in_head'}; } sub start_code { $_[0]{'scratch'} .= ''; } sub end_code { $_[0]{'scratch'} .= ''; } sub handle_code { $_[0]{'scratch'} .= $_[0]->encode_entities( $_[1] ); } sub start_Para { $_[0]{'scratch'} .= '

'; } sub start_Verbatim { $_[0]{'scratch'} = '

';
    push(@{$_[0]{'in_code'}}, 'Verbatim');
    $_[0]->start_code($_[0]{'in_code'}[-1]);
}

sub start_head1 {  $_[0]{'in_head'} = 1; $_[0]{htext} = ''; }
sub start_head2 {  $_[0]{'in_head'} = 2; $_[0]{htext} = ''; }
sub start_head3 {  $_[0]{'in_head'} = 3; $_[0]{htext} = ''; }
sub start_head4 {  $_[0]{'in_head'} = 4; $_[0]{htext} = ''; }

sub start_item_number {
    $_[0]{'scratch'} = "\n" if ($_[0]{'in_li'}->[-1] && pop @{$_[0]{'in_li'}});
    $_[0]{'scratch'} .= '
  • '; push @{$_[0]{'in_li'}}, 1; } sub start_item_bullet { $_[0]{'scratch'} = "

  • \n" if ($_[0]{'in_li'}->[-1] && pop @{$_[0]{'in_li'}}); $_[0]{'scratch'} .= '
  • '; push @{$_[0]{'in_li'}}, 1; } sub start_item_text { # see end_item_text } sub start_over_bullet { $_[0]{'scratch'} = '

      '; push @{$_[0]{'in_li'}}, 0; $_[0]->emit } sub start_over_block { $_[0]{'scratch'} = '
        '; $_[0]->emit } sub start_over_number { $_[0]{'scratch'} = '
          '; push @{$_[0]{'in_li'}}, 0; $_[0]->emit } sub start_over_text { $_[0]{'scratch'} = '
          '; $_[0]{'dl_level'}++; $_[0]{'in_dd'} ||= []; $_[0]->emit } sub end_over_block { $_[0]{'scratch'} .= '
      '; $_[0]->emit } sub end_over_number { $_[0]{'scratch'} = "\n" if ( pop @{$_[0]{'in_li'}} ); $_[0]{'scratch'} .= ''; pop @{$_[0]{'in_li'}}; $_[0]->emit; } sub end_over_bullet { $_[0]{'scratch'} = "\n" if ( pop @{$_[0]{'in_li'}} ); $_[0]{'scratch'} .= '
    '; pop @{$_[0]{'in_li'}}; $_[0]->emit; } sub end_over_text { if ($_[0]{'in_dd'}[ $_[0]{'dl_level'} ]) { $_[0]{'scratch'} = "\n"; $_[0]{'in_dd'}[ $_[0]{'dl_level'} ] = 0; } $_[0]{'scratch'} .= ''; $_[0]{'dl_level'}--; $_[0]->emit; } # . . . . . Now the actual formatters: sub end_Para { $_[0]{'scratch'} .= '

    '; $_[0]->emit } sub end_Verbatim { $_[0]->end_code(pop(@{$_[0]->{'in_code'}})); $_[0]{'scratch'} .= '
  • '; $_[0]->emit; } sub _end_head { my $h = delete $_[0]{in_head}; my $add = $_[0]->html_h_level; $add = 1 unless defined $add; $h += $add - 1; my $id = $_[0]->idify($_[0]{htext}); my $text = $_[0]{scratch}; $_[0]{'scratch'} = $_[0]->backlink && ($h - $add == 0) # backlinks enabled && =head1 ? qq{$text} : qq{$text}; $_[0]->emit; push @{ $_[0]{'to_index'} }, [$h, $id, delete $_[0]{'htext'}]; } sub end_head1 { shift->_end_head(@_); } sub end_head2 { shift->_end_head(@_); } sub end_head3 { shift->_end_head(@_); } sub end_head4 { shift->_end_head(@_); } sub end_item_bullet { $_[0]{'scratch'} .= '

    '; $_[0]->emit } sub end_item_number { $_[0]{'scratch'} .= '

    '; $_[0]->emit } sub end_item_text { # idify and anchor =item content if wanted my $dt_id = $_[0]{'anchor_items'} ? ' id="'. $_[0]->idify($_[0]{'scratch'}) .'"' : ''; # reset scratch my $text = $_[0]{scratch}; $_[0]{'scratch'} = ''; if ($_[0]{'in_dd'}[ $_[0]{'dl_level'} ]) { $_[0]{'scratch'} = "\n"; $_[0]{'in_dd'}[ $_[0]{'dl_level'} ] = 0; } $_[0]{'scratch'} .= qq{$text\n
    }; $_[0]{'in_dd'}[ $_[0]{'dl_level'} ] = 1; $_[0]->emit; } # This handles =begin and =for blocks of all kinds. sub start_for { my ($self, $flags) = @_; push @{ $self->{__region_targets} }, $flags->{target_matching}; $self->{started_for} = 1; $self->{in_for} = 1; unless ($self->__in_literal_xhtml_region) { $self->{scratch} .= '{scratch} .= qq( class="$flags->{target}") if $flags->{target}; $self->{scratch} .= ">\n\n"; } } sub end_for { my ($self) = @_; delete $self->{started_for}; delete $self->{in_for}; if ($self->__in_literal_xhtml_region) { # Remove trailine newlines. $self->{'scratch'} =~ s/\s+\z//s; } else { $self->{'scratch'} .= ''; } pop @{ $self->{__region_targets} }; $self->emit; } sub start_Document { my ($self) = @_; if (defined $self->html_header) { $self->{'scratch'} .= $self->html_header; $self->emit unless $self->html_header eq ""; } else { my ($doctype, $title, $metatags, $bodyid); $doctype = $self->html_doctype || ''; $title = $self->force_title || $self->title || $self->default_title || ''; $metatags = $self->html_header_tags || ''; if (my $css = $self->html_css) { if ($css !~ /encode_entities($css) . '" type="text/css" />'; } else { $metatags .= $css; } } if ($self->html_javascript) { $metatags .= qq{\n'; } $bodyid = $self->backlink ? ' id="_podtop_"' : ''; $self->{'scratch'} .= <<"HTML"; $doctype $title $metatags HTML $self->emit; } } sub end_Document { my ($self) = @_; my $to_index = $self->{'to_index'}; if ($self->index && @{ $to_index } ) { my @out; my $level = 0; my $indent = -1; my $space = ''; my $id = ' id="index"'; for my $h (@{ $to_index }, [0]) { my $target_level = $h->[0]; # Get to target_level by opening or closing ULs if ($level == $target_level) { $out[-1] .= ''; } elsif ($level > $target_level) { $out[-1] .= '' if $out[-1] =~ /^\s+
  • /; while ($level > $target_level) { --$level; push @out, (' ' x --$indent) . '
  • ' if @out && $out[-1] =~ m{^\s+<\/ul}; push @out, (' ' x --$indent) . ''; } push @out, (' ' x --$indent) . '' if $level; } else { while ($level < $target_level) { ++$level; push @out, (' ' x ++$indent) . '
  • ' if @out && $out[-1]=~ /^\s*
      "; $id = ''; } ++$indent; } next unless $level; $space = ' ' x $indent; push @out, sprintf '%s
    • %s', $space, $h->[1], $h->[2]; } # Splice the index in between the HTML headers and the first element. my $offset = defined $self->html_header ? $self->html_header eq '' ? 0 : 1 : 1; splice @{ $self->{'output'} }, $offset, 0, join "\n", @out; } if (defined $self->html_footer) { $self->{'scratch'} .= $self->html_footer; $self->emit unless $self->html_footer eq ""; } else { $self->{'scratch'} .= "\n"; $self->emit; } if ($self->index) { print {$self->{'output_fh'}} join ("\n\n", @{ $self->{'output'} }), "\n\n"; @{$self->{'output'}} = (); } } # Handling code tags sub start_B { $_[0]{'scratch'} .= '' } sub end_B { $_[0]{'scratch'} .= '' } sub start_C { push(@{$_[0]{'in_code'}}, 'C'); $_[0]->start_code($_[0]{'in_code'}[-1]); } sub end_C { $_[0]->end_code(pop(@{$_[0]{'in_code'}})); } sub start_F { $_[0]{'scratch'} .= '' } sub end_F { $_[0]{'scratch'} .= '' } sub start_I { $_[0]{'scratch'} .= '' } sub end_I { $_[0]{'scratch'} .= '' } sub start_L { my ($self, $flags) = @_; my ($type, $to, $section) = @{$flags}{'type', 'to', 'section'}; my $url = $self->encode_entities( $type eq 'url' ? $to : $type eq 'pod' ? $self->resolve_pod_page_link($to, $section) : $type eq 'man' ? $self->resolve_man_page_link($to, $section) : undef ); # If it's an unknown type, use an attribute-less like HTML.pm. $self->{'scratch'} .= '' : '>'); } sub end_L { $_[0]{'scratch'} .= '' } sub start_S { $_[0]{'scratch'} .= '' } sub end_S { $_[0]{'scratch'} .= '' } sub emit { my($self) = @_; if ($self->index) { push @{ $self->{'output'} }, $self->{'scratch'}; } else { print {$self->{'output_fh'}} $self->{'scratch'}, "\n\n"; } $self->{'scratch'} = ''; return; } sub resolve_pod_page_link { my ($self, $to, $section) = @_; return undef unless defined $to || defined $section; if (defined $section) { $section = '#' . $self->idify($self->encode_entities($section), 1); return $section unless defined $to; } else { $section = '' } return ($self->perldoc_url_prefix || '') . $self->encode_entities($to) . $section . ($self->perldoc_url_postfix || ''); } sub resolve_man_page_link { my ($self, $to, $section) = @_; return undef unless defined $to; my ($page, $part) = $to =~ /^([^(]+)(?:[(](\d+)[)])?$/; return undef unless $page; return ($self->man_url_prefix || '') . ($part || 1) . "/" . $self->encode_entities($page) . ($self->man_url_postfix || ''); } sub idify { my ($self, $t, $not_unique) = @_; for ($t) { s/<[^>]+>//g; # Strip HTML. s/&[^;]+;//g; # Strip entities. s/^\s+//; s/\s+$//; # Strip white space. s/^([^a-zA-Z]+)$/pod$1/; # Prepend "pod" if no valid chars. s/^[^a-zA-Z]+//; # First char must be a letter. s/[^-a-zA-Z0-9_:.]+/-/g; # All other chars must be valid. s/[-:.]+$//; # Strip trailing punctuation. } return $t if $not_unique; my $i = ''; $i++ while $self->{ids}{"$t$i"}++; return "$t$i"; } sub batch_mode_page_object_init { my ($self, $batchconvobj, $module, $infile, $outfile, $depth) = @_; $self->batch_mode(1); $self->batch_mode_current_level($depth); return $self; } sub html_header_after_title { } 1; __END__ require 5; package Pod::Simple::HTMLBatch; use strict; use vars qw( $VERSION $HTML_RENDER_CLASS $HTML_EXTENSION $CSS $JAVASCRIPT $SLEEPY $SEARCH_CLASS @ISA ); $VERSION = '3.35'; @ISA = (); # Yup, we're NOT a subclass of Pod::Simple::HTML! # TODO: nocontents stylesheets. Strike some of the color variations? use Pod::Simple::HTML (); BEGIN {*esc = \&Pod::Simple::HTML::esc } use File::Spec (); use Pod::Simple::Search; $SEARCH_CLASS ||= 'Pod::Simple::Search'; BEGIN { if(defined &DEBUG) { } # no-op elsif( defined &Pod::Simple::DEBUG ) { *DEBUG = \&Pod::Simple::DEBUG } else { *DEBUG = sub () {0}; } } $SLEEPY = 1 if !defined $SLEEPY and $^O =~ /mswin|mac/i; # flag to occasionally sleep for $SLEEPY - 1 seconds. $HTML_RENDER_CLASS ||= "Pod::Simple::HTML"; # # Methods beginning with "_" are particularly internal and possibly ugly. # Pod::Simple::_accessorize( __PACKAGE__, 'verbose', # how verbose to be during batch conversion 'html_render_class', # what class to use to render 'search_class', # what to use to search for POD documents 'contents_file', # If set, should be the name of a file (in current directory) # to write the list of all modules to 'index', # will set $htmlpage->index(...) to this (true or false) 'progress', # progress object 'contents_page_start', 'contents_page_end', 'css_flurry', '_css_wad', 'javascript_flurry', '_javascript_wad', 'no_contents_links', # set to true to suppress automatic adding of << links. '_contents', ); # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - # Just so we can run from the command line more easily sub go { @ARGV == 2 or die sprintf( "Usage: perl -M%s -e %s:go indirs outdir\n (or use \"\@INC\" for indirs)\n", __PACKAGE__, __PACKAGE__, ); if(defined($ARGV[1]) and length($ARGV[1])) { my $d = $ARGV[1]; -e $d or die "I see no output directory named \"$d\"\nAborting"; -d $d or die "But \"$d\" isn't a directory!\nAborting"; -w $d or die "Directory \"$d\" isn't writeable!\nAborting"; } __PACKAGE__->batch_convert(@ARGV); } # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - sub new { my $new = bless {}, ref($_[0]) || $_[0]; $new->html_render_class($HTML_RENDER_CLASS); $new->search_class($SEARCH_CLASS); $new->verbose(1 + DEBUG); $new->_contents([]); $new->index(1); $new-> _css_wad([]); $new->css_flurry(1); $new->_javascript_wad([]); $new->javascript_flurry(1); $new->contents_file( 'index' . ($HTML_EXTENSION || $Pod::Simple::HTML::HTML_EXTENSION) ); $new->contents_page_start( join "\n", grep $_, $Pod::Simple::HTML::Doctype_decl, "", "Perl Documentation", $Pod::Simple::HTML::Content_decl, "", "\n\n

      Perl Documentation

      \n" ); # override if you need a different title $new->contents_page_end( sprintf( "\n\n

      Generated by %s v%s under Perl v%s\n
      At %s GMT, which is %s local time.

      \n\n\n", esc( ref($new), eval {$new->VERSION} || $VERSION, $], scalar(gmtime), scalar(localtime), ))); return $new; } # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - sub muse { my $self = shift; if($self->verbose) { print 'T+', int(time() - $self->{'_batch_start_time'}), "s: ", @_, "\n"; } return 1; } # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - sub batch_convert { my($self, $dirs, $outdir) = @_; $self ||= __PACKAGE__; # tolerate being called as an optionless function $self = $self->new unless ref $self; # tolerate being used as a class method if(!defined($dirs) or $dirs eq '' or $dirs eq '@INC' ) { $dirs = ''; } elsif(ref $dirs) { # OK, it's an explicit set of dirs to scan, specified as an arrayref. } else { # OK, it's an explicit set of dirs to scan, specified as a # string like "/thing:/also:/whatever/perl" (":"-delim, as usual) # or, under MSWin, like "c:/thing;d:/also;c:/whatever/perl" (";"-delim!) require Config; my $ps = quotemeta( $Config::Config{'path_sep'} || ":" ); $dirs = [ grep length($_), split qr/$ps/, $dirs ]; } $outdir = $self->filespecsys->curdir unless defined $outdir and length $outdir; $self->_batch_convert_main($dirs, $outdir); } # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - sub _batch_convert_main { my($self, $dirs, $outdir) = @_; # $dirs is either false, or an arrayref. # $outdir is a pathspec. $self->{'_batch_start_time'} ||= time(); $self->muse( "= ", scalar(localtime) ); $self->muse( "Starting batch conversion to \"$outdir\"" ); my $progress = $self->progress; if(!$progress and $self->verbose > 0 and $self->verbose() <= 5) { require Pod::Simple::Progress; $progress = Pod::Simple::Progress->new( ($self->verbose < 2) ? () # Default omission-delay : ($self->verbose == 2) ? 1 # Reduce the omission-delay : 0 # Eliminate the omission-delay ); $self->progress($progress); } if($dirs) { $self->muse(scalar(@$dirs), " dirs to scan: @$dirs"); } else { $self->muse("Scanning \@INC. This could take a minute or two."); } my $mod2path = $self->find_all_pods($dirs ? $dirs : ()); $self->muse("Done scanning."); my $total = keys %$mod2path; unless($total) { $self->muse("No pod found. Aborting batch conversion.\n"); return $self; } $progress and $progress->goal($total); $self->muse("Now converting pod files to HTML.", ($total > 25) ? " This will take a while more." : () ); $self->_spray_css( $outdir ); $self->_spray_javascript( $outdir ); $self->_do_all_batch_conversions($mod2path, $outdir); $progress and $progress->done(sprintf ( "Done converting %d files.", $self->{"__batch_conv_page_count"} )); return $self->_batch_convert_finish($outdir); return $self; } sub _do_all_batch_conversions { my($self, $mod2path, $outdir) = @_; $self->{"__batch_conv_page_count"} = 0; foreach my $module (sort {lc($a) cmp lc($b)} keys %$mod2path) { $self->_do_one_batch_conversion($module, $mod2path, $outdir); sleep($SLEEPY - 1) if $SLEEPY; } return; } sub _batch_convert_finish { my($self, $outdir) = @_; $self->write_contents_file($outdir); $self->muse("Done with batch conversion. $$self{'__batch_conv_page_count'} files done."); $self->muse( "= ", scalar(localtime) ); $self->progress and $self->progress->done("All done!"); return; } # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - sub _do_one_batch_conversion { my($self, $module, $mod2path, $outdir, $outfile) = @_; my $retval; my $total = scalar keys %$mod2path; my $infile = $mod2path->{$module}; my @namelets = grep m/\S/, split "::", $module; # this can stick around in the contents LoL my $depth = scalar @namelets; die "Contentless thingie?! $module $infile" unless @namelets; #sanity $outfile ||= do { my @n = @namelets; $n[-1] .= $HTML_EXTENSION || $Pod::Simple::HTML::HTML_EXTENSION; $self->filespecsys->catfile( $outdir, @n ); }; my $progress = $self->progress; my $page = $self->html_render_class->new; if(DEBUG > 5) { $self->muse($self->{"__batch_conv_page_count"} + 1, "/$total: ", ref($page), " render ($depth) $module => $outfile"); } elsif(DEBUG > 2) { $self->muse($self->{"__batch_conv_page_count"} + 1, "/$total: $module => $outfile") } # Give each class a chance to init the converter: $page->batch_mode_page_object_init($self, $module, $infile, $outfile, $depth) if $page->can('batch_mode_page_object_init'); # Init for the index (TOC), too. $self->batch_mode_page_object_init($page, $module, $infile, $outfile, $depth) if $self->can('batch_mode_page_object_init'); # Now get busy... $self->makepath($outdir => \@namelets); $progress and $progress->reach($self->{"__batch_conv_page_count"}, "Rendering $module"); if( $retval = $page->parse_from_file($infile, $outfile) ) { ++ $self->{"__batch_conv_page_count"} ; $self->note_for_contents_file( \@namelets, $infile, $outfile ); } else { $self->muse("Odd, parse_from_file(\"$infile\", \"$outfile\") returned false."); } $page->batch_mode_page_object_kill($self, $module, $infile, $outfile, $depth) if $page->can('batch_mode_page_object_kill'); # The following isn't a typo. Note that it switches $self and $page. $self->batch_mode_page_object_kill($page, $module, $infile, $outfile, $depth) if $self->can('batch_mode_page_object_kill'); DEBUG > 4 and printf STDERR "%s %sb < $infile %s %sb\n", $outfile, -s $outfile, $infile, -s $infile ; undef($page); return $retval; } # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - sub filespecsys { $_[0]{'_filespecsys'} || 'File::Spec' } # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - sub note_for_contents_file { my($self, $namelets, $infile, $outfile) = @_; # I think the infile and outfile parts are never used. -- SMB # But it's handy to have them around for debugging. if( $self->contents_file ) { my $c = $self->_contents(); push @$c, [ join("::", @$namelets), $infile, $outfile, $namelets ] # 0 1 2 3 ; DEBUG > 3 and print STDERR "Noting @$c[-1]\n"; } return; } #_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_- sub write_contents_file { my($self, $outdir) = @_; my $outfile = $self->_contents_filespec($outdir) || return; $self->muse("Preparing list of modules for ToC"); my($toplevel, # maps toplevelbit => [all submodules] $toplevel_form_freq, # ends up being 'foo' => 'Foo' ) = $self->_prep_contents_breakdown; my $Contents = eval { $self->_wopen($outfile) }; if( $Contents ) { $self->muse( "Writing contents file $outfile" ); } else { warn "Couldn't write-open contents file $outfile: $!\nAbort writing to $outfile at all"; return; } $self->_write_contents_start( $Contents, $outfile, ); $self->_write_contents_middle( $Contents, $outfile, $toplevel, $toplevel_form_freq ); $self->_write_contents_end( $Contents, $outfile, ); return $outfile; } # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - sub _write_contents_start { my($self, $Contents, $outfile) = @_; my $starter = $self->contents_page_start || ''; { my $css_wad = $self->_css_wad_to_markup(1); if( $css_wad ) { $starter =~ s{()}{\n$css_wad\n$1}i; # otherwise nevermind } my $javascript_wad = $self->_javascript_wad_to_markup(1); if( $javascript_wad ) { $starter =~ s{()}{\n$javascript_wad\n$1}i; # otherwise nevermind } } unless(print $Contents $starter, "
      \n" ) { warn "Couldn't print to $outfile: $!\nAbort writing to $outfile at all"; close($Contents); return 0; } return 1; } # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - sub _write_contents_middle { my($self, $Contents, $outfile, $toplevel2submodules, $toplevel_form_freq) = @_; foreach my $t (sort keys %$toplevel2submodules) { my @downlines = sort {$a->[-1] cmp $b->[-1]} @{ $toplevel2submodules->{$t} }; printf $Contents qq[
      %s
      \n
      \n], esc( $t, $toplevel_form_freq->{$t} ) ; my($path, $name); foreach my $e (@downlines) { $name = $e->[0]; $path = join( "/", '.', esc( @{$e->[3]} ) ) . ($HTML_EXTENSION || $Pod::Simple::HTML::HTML_EXTENSION); print $Contents qq{ }, esc($name), "  \n"; } print $Contents "
      \n\n"; } return 1; } # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - sub _write_contents_end { my($self, $Contents, $outfile) = @_; unless( print $Contents "
      \n", $self->contents_page_end || '', ) { warn "Couldn't write to $outfile: $!"; } close($Contents) or warn "Couldn't close $outfile: $!"; return 1; } # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - sub _prep_contents_breakdown { my($self) = @_; my $contents = $self->_contents; my %toplevel; # maps lctoplevelbit => [all submodules] my %toplevel_form_freq; # ends up being 'foo' => 'Foo' # (mapping anycase forms to most freq form) foreach my $entry (@$contents) { my $toplevel = $entry->[0] =~ m/^perl\w*$/ ? 'perl_core_docs' # group all the perlwhatever docs together : $entry->[3][0] # normal case ; ++$toplevel_form_freq{ lc $toplevel }{ $toplevel }; push @{ $toplevel{ lc $toplevel } }, $entry; push @$entry, lc($entry->[0]); # add a sort-order key to the end } foreach my $toplevel (sort keys %toplevel) { my $fgroup = $toplevel_form_freq{$toplevel}; $toplevel_form_freq{$toplevel} = ( sort { $fgroup->{$b} <=> $fgroup->{$a} or $a cmp $b } keys %$fgroup # This hash is extremely unlikely to have more than 4 members, so this # sort isn't so very wasteful )[0]; } return(\%toplevel, \%toplevel_form_freq) if wantarray; return \%toplevel; } # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - sub _contents_filespec { my($self, $outdir) = @_; my $outfile = $self->contents_file; return unless $outfile; return $self->filespecsys->catfile( $outdir, $outfile ); } #_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_- sub makepath { my($self, $outdir, $namelets) = @_; return unless @$namelets > 1; for my $i (0 .. ($#$namelets - 1)) { my $dir = $self->filespecsys->catdir( $outdir, @$namelets[0 .. $i] ); if(-e $dir) { die "$dir exists but not as a directory!?" unless -d $dir; next; } DEBUG > 3 and print STDERR " Making $dir\n"; mkdir $dir, 0777 or die "Can't mkdir $dir: $!\nAborting" ; } return; } #_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_- sub batch_mode_page_object_init { my $self = shift; my($page, $module, $infile, $outfile, $depth) = @_; # TODO: any further options to percolate onto this new object here? $page->default_title($module); $page->index( $self->index ); $page->html_css( $self-> _css_wad_to_markup($depth) ); $page->html_javascript( $self->_javascript_wad_to_markup($depth) ); $self->add_header_backlink($page, $module, $infile, $outfile, $depth); $self->add_footer_backlink($page, $module, $infile, $outfile, $depth); return $self; } # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - sub add_header_backlink { my $self = shift; return if $self->no_contents_links; my($page, $module, $infile, $outfile, $depth) = @_; $page->html_header_after_title( join '', $page->html_header_after_title || '', qq[

      <<

      \n], ) if $self->contents_file ; return; } sub add_footer_backlink { my $self = shift; return if $self->no_contents_links; my($page, $module, $infile, $outfile, $depth) = @_; $page->html_footer( join '', qq[

      <<

      \n], $page->html_footer || '', ) if $self->contents_file ; return; } sub url_up_to_contents { my($self, $depth) = @_; --$depth; return join '/', ('..') x $depth, esc($self->contents_file); } #_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_- sub find_all_pods { my($self, $dirs) = @_; # You can override find_all_pods in a subclass if you want to # do extra filtering or whatnot. But for the moment, we just # pass to modnames2paths: return $self->modnames2paths($dirs); } #_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_- sub modnames2paths { # return a hashref mapping modulenames => paths my($self, $dirs) = @_; my $m2p; { my $search = $self->search_class->new; DEBUG and print STDERR "Searching via $search\n"; $search->verbose(1) if DEBUG > 10; $search->progress( $self->progress->copy->goal(0) ) if $self->progress; $search->shadows(0); # don't bother noting shadowed files $search->inc( $dirs ? 0 : 1 ); $search->survey( $dirs ? @$dirs : () ); $m2p = $search->name2path; die "What, no name2path?!" unless $m2p; } $self->muse("That's odd... no modules found!") unless keys %$m2p; if( DEBUG > 4 ) { print STDERR "Modules found (name => path):\n"; foreach my $m (sort {lc($a) cmp lc($b)} keys %$m2p) { print STDERR " $m $$m2p{$m}\n"; } print STDERR "(total ", scalar(keys %$m2p), ")\n\n"; } elsif( DEBUG ) { print STDERR "Found ", scalar(keys %$m2p), " modules.\n"; } $self->muse( "Found ", scalar(keys %$m2p), " modules." ); # return the Foo::Bar => /whatever/Foo/Bar.pod|pm hashref return $m2p; } #=========================================================================== sub _wopen { # this is abstracted out so that the daemon class can override it my($self, $outpath) = @_; require Symbol; my $out_fh = Symbol::gensym(); DEBUG > 5 and print STDERR "Write-opening to $outpath\n"; return $out_fh if open($out_fh, "> $outpath"); require Carp; Carp::croak("Can't write-open $outpath: $!"); } #========================================================================== sub add_css { my($self, $url, $is_default, $name, $content_type, $media, $_code) = @_; return unless $url; unless($name) { # cook up a reasonable name based on the URL $name = $url; if( $name !~ m/\?/ and $name =~ m{([^/]+)$}s ) { $name = $1; $name =~ s/\.css//i; } } $media ||= 'all'; $content_type ||= 'text/css'; my $bunch = [$url, $name, $content_type, $media, $_code]; if($is_default) { unshift @{ $self->_css_wad }, $bunch } else { push @{ $self->_css_wad }, $bunch } return; } # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - sub _spray_css { my($self, $outdir) = @_; return unless $self->css_flurry(); $self->_gen_css_wad(); my $lol = $self->_css_wad; foreach my $chunk (@$lol) { my $url = $chunk->[0]; my $outfile; if( ref($chunk->[-1]) and $url =~ m{^(_[-a-z0-9_]+\.css$)} ) { $outfile = $self->filespecsys->catfile( $outdir, "$1" ); DEBUG > 5 and print STDERR "Noting $$chunk[0] as a file I'll create.\n"; } else { DEBUG > 5 and print STDERR "OK, noting $$chunk[0] as an external CSS.\n"; # Requires no further attention. next; } #$self->muse( "Writing autogenerated CSS file $outfile" ); my $Cssout = $self->_wopen($outfile); print $Cssout ${$chunk->[-1]} or warn "Couldn't print to $outfile: $!\nAbort writing to $outfile at all"; close($Cssout); DEBUG > 5 and print STDERR "Wrote $outfile\n"; } return; } # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - sub _css_wad_to_markup { my($self, $depth) = @_; my @css = @{ $self->_css_wad || return '' }; return '' unless @css; my $rel = 'stylesheet'; my $out = ''; --$depth; my $uplink = $depth ? ('../' x $depth) : ''; foreach my $chunk (@css) { next unless $chunk and @$chunk; my( $url1, $url2, $title, $type, $media) = ( $self->_maybe_uplink( $chunk->[0], $uplink ), esc(grep !ref($_), @$chunk) ); $out .= qq{\n}; $rel = 'alternate stylesheet'; # alternates = all non-first iterations } return $out; } # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - sub _maybe_uplink { # if the given URL looks relative, return the given uplink string -- # otherwise return emptystring my($self, $url, $uplink) = @_; ($url =~ m{^\./} or $url !~ m{[/\:]} ) ? $uplink : '' # qualify it, if/as needed } # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - sub _gen_css_wad { my $self = $_[0]; my $css_template = $self->_css_template; foreach my $variation ( # Commented out for sake of concision: # # 011n=black_with_red_on_white # 001n=black_with_yellow_on_white # 101n=black_with_green_on_white # 110=white_with_yellow_on_black # 010=white_with_green_on_black # 011=white_with_blue_on_black # 100=white_with_red_on_black '110n=blkbluw', # black_with_blue_on_white '010n=blkmagw', # black_with_magenta_on_white '100n=blkcynw', # black_with_cyan_on_white '101=whtprpk', # white_with_purple_on_black '001=whtnavk', # white_with_navy_blue_on_black '010a=grygrnk', # grey_with_green_on_black '010b=whtgrng', # white_with_green_on_grey '101an=blkgrng', # black_with_green_on_grey '101bn=grygrnw', # grey_with_green_on_white ) { my $outname = $variation; my($flipmode, @swap) = ( ($4 || ''), $1,$2,$3) if $outname =~ s/^([012])([012])([[012])([a-z]*)=?//s; @swap = () if '010' eq join '', @swap; # 010 is a swop-no-op! my $this_css = "/* This file is autogenerated. Do not edit. $variation */\n\n" . $css_template; # Only look at three-digitty colors, for now at least. if( $flipmode =~ m/n/ ) { $this_css =~ s/(#[0-9a-fA-F]{3})\b/_color_negate($1)/eg; $this_css =~ s/\bthin\b/medium/g; } $this_css =~ s<#([0-9a-fA-F])([0-9a-fA-F])([0-9a-fA-F])\b> < join '', '#', ($1,$2,$3)[@swap] >eg if @swap; if( $flipmode =~ m/a/) { $this_css =~ s/#fff\b/#999/gi } # black -> dark grey elsif($flipmode =~ m/b/) { $this_css =~ s/#000\b/#666/gi } # white -> light grey my $name = $outname; $name =~ tr/-_/ /; $self->add_css( "_$outname.css", 0, $name, 0, 0, \$this_css); } # Now a few indexless variations: foreach my $variation ( 'blkbluw', # black_with_blue_on_white 'whtpurk', # white_with_purple_on_black 'whtgrng', # white_with_green_on_grey 'grygrnw', # grey_with_green_on_white ) { my $outname = $variation; my $this_css = join "\n", "/* This file is autogenerated. Do not edit. $outname */\n", "\@import url(\"./_$variation.css\");", ".indexgroup { display: none; }", "\n", ; my $name = $outname; $name =~ tr/-_/ /; $self->add_css( "_$outname.css", 0, $name, 0, 0, \$this_css); } return; } sub _color_negate { my $x = lc $_[0]; $x =~ tr[0123456789abcdef] [fedcba9876543210]; return $x; } #=========================================================================== sub add_javascript { my($self, $url, $content_type, $_code) = @_; return unless $url; push @{ $self->_javascript_wad }, [ $url, $content_type || 'text/javascript', $_code ]; return; } sub _spray_javascript { my($self, $outdir) = @_; return unless $self->javascript_flurry(); $self->_gen_javascript_wad(); my $lol = $self->_javascript_wad; foreach my $script (@$lol) { my $url = $script->[0]; my $outfile; if( ref($script->[-1]) and $url =~ m{^(_[-a-z0-9_]+\.js$)} ) { $outfile = $self->filespecsys->catfile( $outdir, "$1" ); DEBUG > 5 and print STDERR "Noting $$script[0] as a file I'll create.\n"; } else { DEBUG > 5 and print STDERR "OK, noting $$script[0] as an external JavaScript.\n"; next; } #$self->muse( "Writing JavaScript file $outfile" ); my $Jsout = $self->_wopen($outfile); print $Jsout ${$script->[-1]} or warn "Couldn't print to $outfile: $!\nAbort writing to $outfile at all"; close($Jsout); DEBUG > 5 and print STDERR "Wrote $outfile\n"; } return; } sub _gen_javascript_wad { my $self = $_[0]; my $js_code = $self->_javascript || return; $self->add_javascript( "_podly.js", 0, \$js_code); return; } sub _javascript_wad_to_markup { my($self, $depth) = @_; my @scripts = @{ $self->_javascript_wad || return '' }; return '' unless @scripts; my $out = ''; --$depth; my $uplink = $depth ? ('../' x $depth) : ''; foreach my $s (@scripts) { next unless $s and @$s; my( $url1, $url2, $type, $media) = ( $self->_maybe_uplink( $s->[0], $uplink ), esc(grep !ref($_), @$s) ); $out .= qq{\n}; } return $out; } #=========================================================================== sub _css_template { return $CSS } sub _javascript { return $JAVASCRIPT } $CSS = <<'EOCSS'; /* For accessibility reasons, never specify text sizes in px/pt/pc/in/cm/mm */ @media all { .hide { display: none; } } @media print { .noprint, div.indexgroup, .backlinktop, .backlinkbottom { display: none } * { border-color: black !important; color: black !important; background-color: transparent !important; background-image: none !important; } dl.superindex > dd { word-spacing: .6em; } } @media aural, braille, embossed { div.indexgroup { display: none; } /* Too noisy, don't you think? */ dl.superindex > dt:before { content: "Group "; } dl.superindex > dt:after { content: " contains:"; } .backlinktop a:before { content: "Back to contents"; } .backlinkbottom a:before { content: "Back to contents"; } } @media aural { dl.superindex > dt { pause-before: 600ms; } } @media screen, tty, tv, projection { .noscreen { display: none; } a:link { color: #7070ff; text-decoration: underline; } a:visited { color: #e030ff; text-decoration: underline; } a:active { color: #800000; text-decoration: underline; } body.contentspage a { text-decoration: none; } a.u { color: #fff !important; text-decoration: none; } body.pod { margin: 0 5px; color: #fff; background-color: #000; } body.pod h1, body.pod h2, body.pod h3, body.pod h4 { font-family: Tahoma, Verdana, Helvetica, Arial, sans-serif; font-weight: normal; margin-top: 1.2em; margin-bottom: .1em; border-top: thin solid transparent; /* margin-left: -5px; border-left: 2px #7070ff solid; padding-left: 3px; */ } body.pod h1 { border-top-color: #0a0; } body.pod h2 { border-top-color: #080; } body.pod h3 { border-top-color: #040; } body.pod h4 { border-top-color: #010; } p.backlinktop + h1 { border-top: none; margin-top: 0em; } p.backlinktop + h2 { border-top: none; margin-top: 0em; } p.backlinktop + h3 { border-top: none; margin-top: 0em; } p.backlinktop + h4 { border-top: none; margin-top: 0em; } body.pod dt { font-size: 105%; /* just a wee bit more than normal */ } .indexgroup { font-size: 80%; } .backlinktop, .backlinkbottom { margin-left: -5px; margin-right: -5px; background-color: #040; border-top: thin solid #050; border-bottom: thin solid #050; } .backlinktop a, .backlinkbottom a { text-decoration: none; color: #080; background-color: #000; border: thin solid #0d0; } .backlinkbottom { margin-bottom: 0; padding-bottom: 0; } .backlinktop { margin-top: 0; padding-top: 0; } body.contentspage { color: #fff; background-color: #000; } body.contentspage h1 { color: #0d0; margin-left: 1em; margin-right: 1em; text-indent: -.9em; font-family: Tahoma, Verdana, Helvetica, Arial, sans-serif; font-weight: normal; border-top: thin solid #fff; border-bottom: thin solid #fff; text-align: center; } dl.superindex > dt { font-family: Tahoma, Verdana, Helvetica, Arial, sans-serif; font-weight: normal; font-size: 90%; margin-top: .45em; /* margin-bottom: -.15em; */ } dl.superindex > dd { word-spacing: .6em; /* most important rule here! */ } dl.superindex > a:link { text-decoration: none; color: #fff; } .contentsfooty { border-top: thin solid #999; font-size: 90%; } } /* The End */ EOCSS #========================================================================== $JAVASCRIPT = <<'EOJAVASCRIPT'; // From http://www.alistapart.com/articles/alternate/ function setActiveStyleSheet(title) { var i, a, main; for(i=0 ; (a = document.getElementsByTagName("link")[i]) ; i++) { if(a.getAttribute("rel").indexOf("style") != -1 && a.getAttribute("title")) { a.disabled = true; if(a.getAttribute("title") == title) a.disabled = false; } } } function getActiveStyleSheet() { var i, a; for(i=0 ; (a = document.getElementsByTagName("link")[i]) ; i++) { if( a.getAttribute("rel").indexOf("style") != -1 && a.getAttribute("title") && !a.disabled ) return a.getAttribute("title"); } return null; } function getPreferredStyleSheet() { var i, a; for(i=0 ; (a = document.getElementsByTagName("link")[i]) ; i++) { if( a.getAttribute("rel").indexOf("style") != -1 && a.getAttribute("rel").indexOf("alt") == -1 && a.getAttribute("title") ) return a.getAttribute("title"); } return null; } function createCookie(name,value,days) { if (days) { var date = new Date(); date.setTime(date.getTime()+(days*24*60*60*1000)); var expires = "; expires="+date.toGMTString(); } else expires = ""; document.cookie = name+"="+value+expires+"; path=/"; } function readCookie(name) { var nameEQ = name + "="; var ca = document.cookie.split(';'); for(var i=0 ; i < ca.length ; i++) { var c = ca[i]; while (c.charAt(0)==' ') c = c.substring(1,c.length); if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length,c.length); } return null; } window.onload = function(e) { var cookie = readCookie("style"); var title = cookie ? cookie : getPreferredStyleSheet(); setActiveStyleSheet(title); } window.onunload = function(e) { var title = getActiveStyleSheet(); createCookie("style", title, 365); } var cookie = readCookie("style"); var title = cookie ? cookie : getPreferredStyleSheet(); setActiveStyleSheet(title); // The End EOJAVASCRIPT # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 1; __END__ require 5; package Pod::Simple::TextContent; use strict; use Carp (); use Pod::Simple (); use vars qw( @ISA $VERSION ); $VERSION = '3.35'; @ISA = ('Pod::Simple'); sub new { my $self = shift; my $new = $self->SUPER::new(@_); $new->{'output_fh'} ||= *STDOUT{IO}; $new->nix_X_codes(1); return $new; } #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ sub _handle_element_start { print {$_[0]{'output_fh'}} "\n" unless $_[1] =~ m/^[A-Z]$/s; return; } sub _handle_text { $_[1] =~ s/$Pod::Simple::shy//g; $_[1] =~ s/$Pod::Simple::nbsp/ /g; print {$_[0]{'output_fh'}} $_[1]; return; } sub _handle_element_end { print {$_[0]{'output_fh'}} "\n" unless $_[1] =~ m/^[A-Z]$/s; return; } #@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ 1; __END__ require 5; package Pod::Simple::LinkSection; # Based somewhat dimly on Array::Autojoin use vars qw($VERSION ); $VERSION = '3.35'; use strict; use Pod::Simple::BlackBox; use vars qw($VERSION ); $VERSION = '3.35'; use overload( # So it'll stringify nice '""' => \&Pod::Simple::BlackBox::stringify_lol, 'bool' => \&Pod::Simple::BlackBox::stringify_lol, # '.=' => \&tack_on, # grudgingly support 'fallback' => 1, # turn on cleverness ); sub tack_on { $_[0] = ['', {}, "$_[0]" ]; return $_[0][2] .= $_[1]; } sub as_string { goto &Pod::Simple::BlackBox::stringify_lol; } sub stringify { goto &Pod::Simple::BlackBox::stringify_lol; } sub new { my $class = shift; $class = ref($class) || $class; my $new; if(@_ == 1) { if (!ref($_[0] || '')) { # most common case: one bare string return bless ['', {}, $_[0] ], $class; } elsif( ref($_[0] || '') eq 'ARRAY') { $new = [ @{ $_[0] } ]; } else { Carp::croak( "$class new() doesn't know to clone $new" ); } } else { # misc stuff $new = [ '', {}, @_ ]; } # By now it's a treelet: [ 'foo', {}, ... ] foreach my $x (@$new) { if(ref($x || '') eq 'ARRAY') { $x = $class->new($x); # recurse } elsif(ref($x || '') eq 'HASH') { $x = { %$x }; } # otherwise leave it. } return bless $new, $class; } # Not much in this class is likely to be link-section specific -- # but it just so happens that link-sections are about the only treelets # that are exposed to the user. 1; __END__ # TODO: let it be an option whether a given subclass even wants little treelets? __END__ require 5; package Pod::Simple::Progress; $VERSION = '3.35'; use strict; # Objects of this class are used for noting progress of an # operation every so often. Messages delivered more often than that # are suppressed. # # There's actually nothing in here that's specific to Pod processing; # but it's ad-hoc enough that I'm not willing to give it a name that # implies that it's generally useful, like "IO::Progress" or something. # # -- sburke # #-------------------------------------------------------------------------- sub new { my($class,$delay) = @_; my $self = bless {'quiet_until' => 1}, ref($class) || $class; $self->to(*STDOUT{IO}); $self->delay(defined($delay) ? $delay : 5); return $self; } sub copy { my $orig = shift; bless {%$orig, 'quiet_until' => 1}, ref($orig); } #-------------------------------------------------------------------------- sub reach { my($self, $point, $note) = @_; if( (my $now = time) >= $self->{'quiet_until'}) { my $goal; my $to = $self->{'to'}; print $to join('', ($self->{'quiet_until'} == 1) ? () : '... ', (defined $point) ? ( '#', ($goal = $self->{'goal'}) ? ( ' ' x (length($goal) - length($point)), $point, '/', $goal, ) : $point, $note ? ': ' : (), ) : (), $note || '', "\n" ); $self->{'quiet_until'} = $now + $self->{'delay'}; } return $self; } #-------------------------------------------------------------------------- sub done { my($self, $note) = @_; $self->{'quiet_until'} = 1; return $self->reach( undef, $note ); } #-------------------------------------------------------------------------- # Simple accessors: sub delay { return $_[0]{'delay'} if @_ == 1; $_[0]{'delay'} = $_[1]; return $_[0] } sub goal { return $_[0]{'goal' } if @_ == 1; $_[0]{'goal' } = $_[1]; return $_[0] } sub to { return $_[0]{'to' } if @_ == 1; $_[0]{'to' } = $_[1]; return $_[0] } #-------------------------------------------------------------------------- unless(caller) { # Simple self-test: my $p = __PACKAGE__->new->goal(5); $p->reach(1, "Primus!"); sleep 1; $p->reach(2, "Secundus!"); sleep 3; $p->reach(3, "Tertius!"); sleep 5; $p->reach(4); $p->reach(5, "Quintus!"); sleep 1; $p->done("All done"); } #-------------------------------------------------------------------------- 1; __END__ require 5; package Pod::Simple::Debug; use strict; use vars qw($VERSION ); $VERSION = '3.35'; sub import { my($value,$variable); if(@_ == 2) { $value = $_[1]; } elsif(@_ == 3) { ($variable, $value) = @_[1,2]; ($variable, $value) = ($value, $variable) if defined $value and ref($value) eq 'SCALAR' and not(defined $variable and ref($variable) eq 'SCALAR') ; # tolerate getting it backwards unless( defined $variable and ref($variable) eq 'SCALAR') { require Carp; Carp::croak("Usage:\n use Pod::Simple::Debug (NUMVAL)\nor" . "\n use Pod::Simple::Debug (\\\$var, STARTNUMVAL)\nAborting"); } } else { require Carp; Carp::croak("Usage:\n use Pod::Simple::Debug (NUMVAL)\nor" . "\n use Pod::Simple::Debug (\\\$var, STARTNUMVAL)\nAborting"); } if( defined &Pod::Simple::DEBUG ) { require Carp; Carp::croak("It's too late to call Pod::Simple::Debug -- " . "Pod::Simple has already loaded\nAborting"); } $value = 0 unless defined $value; unless($value =~ m/^-?\d+$/) { require Carp; Carp::croak( "$value isn't a numeric value." . "\nUsage:\n use Pod::Simple::Debug (NUMVAL)\nor" . "\n use Pod::Simple::Debug (\\\$var, STARTNUMVAL)\nAborting"); } if( defined $variable ) { # make a not-really-constant *Pod::Simple::DEBUG = sub () { $$variable } ; $$variable = $value; print STDERR "# Starting Pod::Simple::DEBUG = non-constant $variable with val $value\n"; } else { *Pod::Simple::DEBUG = eval " sub () { $value } "; print STDERR "# Starting Pod::Simple::DEBUG = $value\n"; } require Pod::Simple; return; } 1; __END__ require 5; package Pod::Simple::PullParserEndToken; use Pod::Simple::PullParserToken (); use strict; use vars qw(@ISA $VERSION); @ISA = ('Pod::Simple::PullParserToken'); $VERSION = '3.35'; sub new { # Class->new(tagname); my $class = shift; return bless ['end', @_], ref($class) || $class; } # Purely accessors: sub tagname { (@_ == 2) ? ($_[0][1] = $_[1]) : $_[0][1] } sub tag { shift->tagname(@_) } # shortcut: sub is_tagname { $_[0][1] eq $_[1] } sub is_tag { shift->is_tagname(@_) } 1; __END__ require 5; package Pod::Simple::PullParser; $VERSION = '3.35'; use Pod::Simple (); BEGIN {@ISA = ('Pod::Simple')} use strict; use Carp (); use Pod::Simple::PullParserStartToken; use Pod::Simple::PullParserEndToken; use Pod::Simple::PullParserTextToken; BEGIN { *DEBUG = \&Pod::Simple::DEBUG unless defined &DEBUG } __PACKAGE__->_accessorize( 'source_fh', # the filehandle we're reading from 'source_scalar_ref', # the scalarref we're reading from 'source_arrayref', # the arrayref we're reading from ); #@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ # # And here is how we implement a pull-parser on top of a push-parser... sub filter { my($self, $source) = @_; $self = $self->new unless ref $self; $source = *STDIN{IO} unless defined $source; $self->set_source($source); $self->output_fh(*STDOUT{IO}); $self->run; # define run() in a subclass if you want to use filter()! return $self; } # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - sub parse_string_document { my $this = shift; $this->set_source(\ $_[0]); $this->run; } sub parse_file { my($this, $filename) = @_; $this->set_source($filename); $this->run; } # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - # In case anyone tries to use them: sub run { use Carp (); if( __PACKAGE__ eq ref($_[0]) || $_[0]) { # I'm not being subclassed! Carp::croak "You can call run() only on subclasses of " . __PACKAGE__; } else { Carp::croak join '', "You can't call run() because ", ref($_[0]) || $_[0], " didn't define a run() method"; } } sub parse_lines { use Carp (); Carp::croak "Use set_source with ", __PACKAGE__, " and subclasses, not parse_lines"; } sub parse_line { use Carp (); Carp::croak "Use set_source with ", __PACKAGE__, " and subclasses, not parse_line"; } #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ sub new { my $class = shift; my $self = $class->SUPER::new(@_); die "Couldn't construct for $class" unless $self; $self->{'token_buffer'} ||= []; $self->{'start_token_class'} ||= 'Pod::Simple::PullParserStartToken'; $self->{'text_token_class'} ||= 'Pod::Simple::PullParserTextToken'; $self->{'end_token_class'} ||= 'Pod::Simple::PullParserEndToken'; DEBUG > 1 and print STDERR "New pullparser object: $self\n"; return $self; } # ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ sub get_token { my $self = shift; DEBUG > 1 and print STDERR "\nget_token starting up on $self.\n"; DEBUG > 2 and print STDERR " Items in token-buffer (", scalar( @{ $self->{'token_buffer'} } ) , ") :\n", map( " " . $_->dump . "\n", @{ $self->{'token_buffer'} } ), @{ $self->{'token_buffer'} } ? '' : ' (no tokens)', "\n" ; until( @{ $self->{'token_buffer'} } ) { DEBUG > 3 and print STDERR "I need to get something into my empty token buffer...\n"; if($self->{'source_dead'}) { DEBUG and print STDERR "$self 's source is dead.\n"; push @{ $self->{'token_buffer'} }, undef; } elsif(exists $self->{'source_fh'}) { my @lines; my $fh = $self->{'source_fh'} || Carp::croak('You have to call set_source before you can call get_token'); DEBUG and print STDERR "$self 's source is filehandle $fh.\n"; # Read those many lines at a time for(my $i = Pod::Simple::MANY_LINES; $i--;) { DEBUG > 3 and print STDERR " Fetching a line from source filehandle $fh...\n"; local $/ = $Pod::Simple::NL; push @lines, scalar(<$fh>); # readline DEBUG > 3 and print STDERR " Line is: ", defined($lines[-1]) ? $lines[-1] : "\n"; unless( defined $lines[-1] ) { DEBUG and print STDERR "That's it for that source fh! Killing.\n"; delete $self->{'source_fh'}; # so it can be GC'd last; } # but pass thru the undef, which will set source_dead to true # TODO: look to see if $lines[-1] is =encoding, and if so, # do horribly magic things } if(DEBUG > 8) { print STDERR "* I've gotten ", scalar(@lines), " lines:\n"; foreach my $l (@lines) { if(defined $l) { print STDERR " line {$l}\n"; } else { print STDERR " line undef\n"; } } print STDERR "* end of ", scalar(@lines), " lines\n"; } $self->SUPER::parse_lines(@lines); } elsif(exists $self->{'source_arrayref'}) { DEBUG and print STDERR "$self 's source is arrayref $self->{'source_arrayref'}, with ", scalar(@{$self->{'source_arrayref'}}), " items left in it.\n"; DEBUG > 3 and print STDERR " Fetching ", Pod::Simple::MANY_LINES, " lines.\n"; $self->SUPER::parse_lines( splice @{ $self->{'source_arrayref'} }, 0, Pod::Simple::MANY_LINES ); unless( @{ $self->{'source_arrayref'} } ) { DEBUG and print STDERR "That's it for that source arrayref! Killing.\n"; $self->SUPER::parse_lines(undef); delete $self->{'source_arrayref'}; # so it can be GC'd } # to make sure that an undef is always sent to signal end-of-stream } elsif(exists $self->{'source_scalar_ref'}) { DEBUG and print STDERR "$self 's source is scalarref $self->{'source_scalar_ref'}, with ", length(${ $self->{'source_scalar_ref'} }) - (pos(${ $self->{'source_scalar_ref'} }) || 0), " characters left to parse.\n"; DEBUG > 3 and print STDERR " Fetching a line from source-string...\n"; if( ${ $self->{'source_scalar_ref'} } =~ m/([^\n\r]*)((?:\r?\n)?)/g ) { #print(">> $1\n"), $self->SUPER::parse_lines($1) if length($1) or length($2) or pos( ${ $self->{'source_scalar_ref'} }) != length( ${ $self->{'source_scalar_ref'} }); # I.e., unless it's a zero-length "empty line" at the very # end of "foo\nbar\n" (i.e., between the \n and the EOS). } else { # that's the end. Byebye $self->SUPER::parse_lines(undef); delete $self->{'source_scalar_ref'}; DEBUG and print STDERR "That's it for that source scalarref! Killing.\n"; } } else { die "What source??"; } } DEBUG and print STDERR "get_token about to return ", Pod::Simple::pretty( @{$self->{'token_buffer'}} ? $self->{'token_buffer'}[-1] : undef ), "\n"; return shift @{$self->{'token_buffer'}}; # that's an undef if empty } sub unget_token { my $self = shift; DEBUG and print STDERR "Ungetting ", scalar(@_), " tokens: ", @_ ? "@_\n" : "().\n"; foreach my $t (@_) { Carp::croak "Can't unget that, because it's not a token -- it's undef!" unless defined $t; Carp::croak "Can't unget $t, because it's not a token -- it's a string!" unless ref $t; Carp::croak "Can't unget $t, because it's not a token object!" unless UNIVERSAL::can($t, 'type'); } unshift @{$self->{'token_buffer'}}, @_; DEBUG > 1 and print STDERR "Token buffer now has ", scalar(@{$self->{'token_buffer'}}), " items in it.\n"; return; } #@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ # $self->{'source_filename'} = $source; sub set_source { my $self = shift @_; return $self->{'source_fh'} unless @_; Carp::croak("Cannot assign new source to pull parser; create a new instance, instead") if $self->{'source_fh'} || $self->{'source_scalar_ref'} || $self->{'source_arrayref'}; my $handle; if(!defined $_[0]) { Carp::croak("Can't use empty-string as a source for set_source"); } elsif(ref(\( $_[0] )) eq 'GLOB') { $self->{'source_filename'} = '' . ($handle = $_[0]); DEBUG and print STDERR "$self 's source is glob $_[0]\n"; # and fall thru } elsif(ref( $_[0] ) eq 'SCALAR') { $self->{'source_scalar_ref'} = $_[0]; DEBUG and print STDERR "$self 's source is scalar ref $_[0]\n"; return; } elsif(ref( $_[0] ) eq 'ARRAY') { $self->{'source_arrayref'} = $_[0]; DEBUG and print STDERR "$self 's source is array ref $_[0]\n"; return; } elsif(ref $_[0]) { $self->{'source_filename'} = '' . ($handle = $_[0]); DEBUG and print STDERR "$self 's source is fh-obj $_[0]\n"; } elsif(!length $_[0]) { Carp::croak("Can't use empty-string as a source for set_source"); } else { # It's a filename! DEBUG and print STDERR "$self 's source is filename $_[0]\n"; { local *PODSOURCE; open(PODSOURCE, "<$_[0]") || Carp::croak "Can't open $_[0]: $!"; $handle = *PODSOURCE{IO}; } $self->{'source_filename'} = $_[0]; DEBUG and print STDERR " Its name is $_[0].\n"; # TODO: file-discipline things here! } $self->{'source_fh'} = $handle; DEBUG and print STDERR " Its handle is $handle\n"; return 1; } # ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ sub get_title_short { shift->get_short_title(@_) } # alias sub get_short_title { my $title = shift->get_title(@_); $title = $1 if $title =~ m/^(\S{1,60})\s+--?\s+./s; # turn "Foo::Bar -- bars for your foo" into "Foo::Bar" return $title; } sub get_title { shift->_get_titled_section( 'NAME', max_token => 50, desperate => 1, @_) } sub get_version { shift->_get_titled_section( 'VERSION', max_token => 400, accept_verbatim => 1, max_content_length => 3_000, @_, ); } sub get_description { shift->_get_titled_section( 'DESCRIPTION', max_token => 400, max_content_length => 3_000, @_, ) } sub get_authors { shift->get_author(@_) } # a harmless alias sub get_author { my $this = shift; # Max_token is so high because these are # typically at the end of the document: $this->_get_titled_section('AUTHOR' , max_token => 10_000, @_) || $this->_get_titled_section('AUTHORS', max_token => 10_000, @_); } #-------------------------------------------------------------------------- sub _get_titled_section { # Based on a get_title originally contributed by Graham Barr my($self, $titlename, %options) = (@_); my $max_token = delete $options{'max_token'}; my $desperate_for_title = delete $options{'desperate'}; my $accept_verbatim = delete $options{'accept_verbatim'}; my $max_content_length = delete $options{'max_content_length'}; my $nocase = delete $options{'nocase'}; $max_content_length = 120 unless defined $max_content_length; Carp::croak( "Unknown " . ((1 == keys %options) ? "option: " : "options: ") . join " ", map "[$_]", sort keys %options ) if keys %options; my %content_containers; $content_containers{'Para'} = 1; if($accept_verbatim) { $content_containers{'Verbatim'} = 1; $content_containers{'VerbatimFormatted'} = 1; } my $token_count = 0; my $title; my @to_unget; my $state = 0; my $depth = 0; Carp::croak "What kind of titlename is \"$titlename\"?!" unless defined $titlename and $titlename =~ m/^[A-Z ]{1,60}$/s; #sanity my $titlename_re = quotemeta($titlename); my $head1_text_content; my $para_text_content; my $skipX; while( ++$token_count <= ($max_token || 1_000_000) and defined(my $token = $self->get_token) ) { push @to_unget, $token; if ($state == 0) { # seeking =head1 if( $token->is_start and $token->tagname eq 'head1' ) { DEBUG and print STDERR " Found head1. Seeking content...\n"; ++$state; $head1_text_content = ''; } } elsif($state == 1) { # accumulating text until end of head1 if( $token->is_text ) { unless ($skipX) { DEBUG and print STDERR " Adding \"", $token->text, "\" to head1-content.\n"; $head1_text_content .= $token->text; } } elsif( $token->is_tagname('X') ) { # We're going to want to ignore X<> stuff. $skipX = $token->is_start; DEBUG and print STDERR +($skipX ? 'Start' : 'End'), 'ing ignoring of X<> tag'; } elsif( $token->is_end and $token->tagname eq 'head1' ) { DEBUG and print STDERR " Found end of head1. Considering content...\n"; $head1_text_content = uc $head1_text_content if $nocase; if($head1_text_content eq $titlename or $head1_text_content =~ m/\($titlename_re\)/s # We accept "=head1 Nomen Modularis (NAME)" for sake of i18n ) { DEBUG and print STDERR " Yup, it was $titlename. Seeking next para-content...\n"; ++$state; } elsif( $desperate_for_title # if we're so desperate we'll take the first # =head1's content as a title and $head1_text_content =~ m/\S/ and $head1_text_content !~ m/^[ A-Z]+$/s and $head1_text_content !~ m/\((?: NAME | TITLE | VERSION | AUTHORS? | DESCRIPTION | SYNOPSIS | COPYRIGHT | LICENSE | NOTES? | FUNCTIONS? | METHODS? | CAVEATS? | BUGS? | SEE\ ALSO | SWITCHES | ENVIRONMENT )\)/sx # avoid accepting things like =head1 Thingy Thongy (DESCRIPTION) and ($max_content_length ? (length($head1_text_content) <= $max_content_length) # sanity : 1) ) { # Looks good; trim it ($title = $head1_text_content) =~ s/\s+$//; DEBUG and print STDERR " It looks titular: \"$title\".\n\n Using that.\n"; last; } else { --$state; DEBUG and print STDERR " Didn't look titular ($head1_text_content).\n", "\n Dropping back to seeking-head1-content mode...\n"; } } } elsif($state == 2) { # seeking start of para (which must immediately follow) if($token->is_start and $content_containers{ $token->tagname }) { DEBUG and print STDERR " Found start of Para. Accumulating content...\n"; $para_text_content = ''; ++$state; } else { DEBUG and print " Didn't see an immediately subsequent start-Para. Reseeking H1\n"; $state = 0; } } elsif($state == 3) { # accumulating text until end of Para if( $token->is_text ) { DEBUG and print STDERR " Adding \"", $token->text, "\" to para-content.\n"; $para_text_content .= $token->text; # and keep looking } elsif( $token->is_end and $content_containers{ $token->tagname } ) { DEBUG and print STDERR " Found end of Para. Considering content: ", $para_text_content, "\n"; if( $para_text_content =~ m/\S/ and ($max_content_length ? (length($para_text_content) <= $max_content_length) : 1) ) { # Some minimal sanity constraints, I think. DEBUG and print STDERR " It looks contentworthy, I guess. Using it.\n"; $title = $para_text_content; last; } else { DEBUG and print STDERR " Doesn't look at all contentworthy!\n Giving up.\n"; undef $title; last; } } } else { die "IMPOSSIBLE STATE $state!\n"; # should never happen } } # Put it all back! $self->unget_token(@to_unget); if(DEBUG) { if(defined $title) { print STDERR " Returning title <$title>\n" } else { print STDERR "Returning title <>\n" } } return '' unless defined $title; $title =~ s/^\s+//; return $title; } #@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ # # Methods that actually do work at parse-time: sub _handle_element_start { my $self = shift; # leaving ($element_name, $attr_hash_r) DEBUG > 2 and print STDERR "++ $_[0] (", map("<$_> ", %{$_[1]}), ")\n"; push @{ $self->{'token_buffer'} }, $self->{'start_token_class'}->new(@_); return; } sub _handle_text { my $self = shift; # leaving ($text) DEBUG > 2 and print STDERR "== $_[0]\n"; push @{ $self->{'token_buffer'} }, $self->{'text_token_class'}->new(@_); return; } sub _handle_element_end { my $self = shift; # leaving ($element_name); DEBUG > 2 and print STDERR "-- $_[0]\n"; push @{ $self->{'token_buffer'} }, $self->{'end_token_class'}->new(@_); return; } #@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ 1; __END__ JUNK: sub _old_get_title { # some witchery in here my $self = $_[0]; my $title; my @to_unget; while(1) { push @to_unget, $self->get_token; unless(defined $to_unget[-1]) { # whoops, short doc! pop @to_unget; last; } DEBUG and print STDERR "-Got token ", $to_unget[-1]->dump, "\n"; (DEBUG and print STDERR "Too much in the buffer.\n"), last if @to_unget > 25; # sanity my $pattern = ''; if( #$to_unget[-1]->type eq 'end' #and $to_unget[-1]->tagname eq 'Para' #and ($pattern = join('', map {; ($_->type eq 'start') ? ("<" . $_->tagname .">") : ($_->type eq 'end' ) ? ("tagname .">") : ($_->type eq 'text' ) ? ($_->text =~ m<^([A-Z]+)$>s ? $1 : 'X') : "BLORP" } @to_unget )) =~ m{NAME(X|)+$}s ) { # Whee, it fits the pattern DEBUG and print STDERR "Seems to match =head1 NAME pattern.\n"; $title = ''; foreach my $t (reverse @to_unget) { last if $t->type eq 'start' and $t->tagname eq 'Para'; $title = $t->text . $title if $t->type eq 'text'; } undef $title if $title =~ m<^\s*$>; # make sure it's contentful! last; } elsif ($pattern =~ m{(.+)$} and !( $1 eq '1' and $2 eq 'NAME' ) ) { # Well, it fits a fallback pattern DEBUG and print STDERR "Seems to match NAMEless pattern.\n"; $title = ''; foreach my $t (reverse @to_unget) { last if $t->type eq 'start' and $t->tagname =~ m/^head\d$/s; $title = $t->text . $title if $t->type eq 'text'; } undef $title if $title =~ m<^\s*$>; # make sure it's contentful! last; } else { DEBUG and $pattern and print STDERR "Leading pattern: $pattern\n"; } } # Put it all back: $self->unget_token(@to_unget); if(DEBUG) { if(defined $title) { print STDERR " Returning title <$title>\n" } else { print STDERR "Returning title <>\n" } } return '' unless defined $title; return $title; } require 5; package Pod::Simple::PullParserStartToken; use Pod::Simple::PullParserToken (); use strict; use vars qw(@ISA $VERSION); @ISA = ('Pod::Simple::PullParserToken'); $VERSION = '3.35'; sub new { # Class->new(tagname, optional_attrhash); my $class = shift; return bless ['start', @_], ref($class) || $class; } # Purely accessors: sub tagname { (@_ == 2) ? ($_[0][1] = $_[1]) : $_[0][1] } sub tag { shift->tagname(@_) } sub is_tagname { $_[0][1] eq $_[1] } sub is_tag { shift->is_tagname(@_) } sub attr_hash { $_[0][2] ||= {} } sub attr { if(@_ == 2) { # Reading: $token->attr('attrname') ${$_[0][2] || return undef}{ $_[1] }; } elsif(@_ > 2) { # Writing: $token->attr('attrname', 'newval') ${$_[0][2] ||= {}}{ $_[1] } = $_[2]; } else { require Carp; Carp::croak( 'usage: $object->attr("val") or $object->attr("key", "newval")'); return undef; } } 1; __END__ require 5; package Pod::Simple::HTML; use strict; use Pod::Simple::PullParser (); use vars qw( @ISA %Tagmap $Computerese $LamePad $Linearization_Limit $VERSION $Perldoc_URL_Prefix $Perldoc_URL_Postfix $Man_URL_Prefix $Man_URL_Postfix $Title_Prefix $Title_Postfix $HTML_EXTENSION %ToIndex $Doctype_decl $Content_decl ); @ISA = ('Pod::Simple::PullParser'); $VERSION = '3.35'; BEGIN { if(defined &DEBUG) { } # no-op elsif( defined &Pod::Simple::DEBUG ) { *DEBUG = \&Pod::Simple::DEBUG } else { *DEBUG = sub () {0}; } } $Doctype_decl ||= ''; # No. Just No. Don't even ask me for it. # qq{\n}; $Content_decl ||= q{}; $HTML_EXTENSION = '.html' unless defined $HTML_EXTENSION; $Computerese = "" unless defined $Computerese; $LamePad = '' unless defined $LamePad; $Linearization_Limit = 120 unless defined $Linearization_Limit; # headings/items longer than that won't get an $Perldoc_URL_Prefix = 'http://search.cpan.org/perldoc?' unless defined $Perldoc_URL_Prefix; $Perldoc_URL_Postfix = '' unless defined $Perldoc_URL_Postfix; $Man_URL_Prefix = 'http://man.he.net/man'; $Man_URL_Postfix = ''; $Title_Prefix = '' unless defined $Title_Prefix; $Title_Postfix = '' unless defined $Title_Postfix; %ToIndex = map {; $_ => 1 } qw(head1 head2 head3 head4 ); # item-text # 'item-text' stuff in the index doesn't quite work, and may # not be a good idea anyhow. __PACKAGE__->_accessorize( 'perldoc_url_prefix', # In turning L into http://whatever/Foo%3a%3aBar, what # to put before the "Foo%3a%3aBar". # (for singleton mode only?) 'perldoc_url_postfix', # what to put after "Foo%3a%3aBar" in the URL. Normally "". 'man_url_prefix', # In turning L into http://whatever/man/1/crontab, what # to put before the "1/crontab". 'man_url_postfix', # what to put after the "1/crontab" in the URL. Normally "". 'batch_mode', # whether we're in batch mode 'batch_mode_current_level', # When in batch mode, how deep the current module is: 1 for "LWP", # 2 for "LWP::Procotol", 3 for "LWP::Protocol::GHTTP", etc 'title_prefix', 'title_postfix', # What to put before and after the title in the head. # Should already be &-escaped 'html_h_level', 'html_header_before_title', 'html_header_after_title', 'html_footer', 'top_anchor', 'index', # whether to add an index at the top of each page # (actually it's a table-of-contents, but we'll call it an index, # out of apparently longstanding habit) 'html_css', # URL of CSS file to point to 'html_javascript', # URL of Javascript file to point to 'force_title', # should already be &-escaped 'default_title', # should already be &-escaped ); #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ my @_to_accept; %Tagmap = ( 'Verbatim' => "\n", '/Verbatim' => "\n", 'VerbatimFormatted' => "\n", '/VerbatimFormatted' => "\n", 'VerbatimB' => "", '/VerbatimB' => "", 'VerbatimI' => "", '/VerbatimI' => "", 'VerbatimBI' => "", '/VerbatimBI' => "", 'Data' => "\n", '/Data' => "\n", 'head1' => "\n

      ", # And also stick in an 'head2' => "\n

      ", # '' 'head3' => "\n

      ", # '' 'head4' => "\n

      ", # '' '/head1' => "

      \n", '/head2' => "

      \n", '/head3' => "\n", '/head4' => "\n", 'X' => "", changes(qw( Para=p B=b I=i over-bullet=ul over-number=ol over-text=dl over-block=blockquote item-bullet=li item-number=li item-text=dt )), changes2( map {; m/^([-a-z]+)/s && push @_to_accept, $1; $_ } qw[ sample=samp definition=dfn keyboard=kbd variable=var citation=cite abbreviation=abbr acronym=acronym subscript=sub superscript=sup big=big small=small underline=u strikethrough=s preformat=pre teletype=tt ] # no point in providing a way to get ..., I think ), '/item-bullet' => "
    • $LamePad\n", '/item-number' => "$LamePad\n", '/item-text' => "$LamePad\n", 'item-body' => "\n
      ", '/item-body' => "
      \n", 'B' => "", '/B' => "", 'I' => "", '/I' => "", 'F' => "", '/F' => "", 'C' => "", '/C' => "
      ", 'L' => "", # ideally never used! '/L' => "", ); sub changes { return map {; m/^([-_:0-9a-zA-Z]+)=([-_:0-9a-zA-Z]+)$/s ? ( $1, => "\n<$2>", "/$1", => "\n" ) : die "Funky $_" } @_; } sub changes2 { return map {; m/^([-_:0-9a-zA-Z]+)=([-_:0-9a-zA-Z]+)$/s ? ( $1, => "<$2>", "/$1", => "" ) : die "Funky $_" } @_; } #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ sub go { Pod::Simple::HTML->parse_from_file(@ARGV); exit 0 } # Just so we can run from the command line. No options. # For that, use perldoc! #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ sub new { my $new = shift->SUPER::new(@_); #$new->nix_X_codes(1); $new->nbsp_for_S(1); $new->accept_targets( 'html', 'HTML' ); $new->accept_codes('VerbatimFormatted'); $new->accept_codes(@_to_accept); DEBUG > 2 and print STDERR "To accept: ", join(' ',@_to_accept), "\n"; $new->perldoc_url_prefix( $Perldoc_URL_Prefix ); $new->perldoc_url_postfix( $Perldoc_URL_Postfix ); $new->man_url_prefix( $Man_URL_Prefix ); $new->man_url_postfix( $Man_URL_Postfix ); $new->title_prefix( $Title_Prefix ); $new->title_postfix( $Title_Postfix ); $new->html_header_before_title( qq[$Doctype_decl] ); $new->html_header_after_title( join "\n" => "", $Content_decl, "\n", $new->version_tag_comment, "\n", ); $new->html_footer( qq[\n\n\n\n] ); $new->top_anchor( "\n" ); $new->{'Tagmap'} = {%Tagmap}; return $new; } sub __adjust_html_h_levels { my ($self) = @_; my $Tagmap = $self->{'Tagmap'}; my $add = $self->html_h_level; return unless defined $add; return if ($self->{'Adjusted_html_h_levels'}||0) == $add; $add -= 1; for (1 .. 4) { $Tagmap->{"head$_"} =~ s/$_/$_ + $add/e; $Tagmap->{"/head$_"} =~ s/$_/$_ + $add/e; } } sub batch_mode_page_object_init { my($self, $batchconvobj, $module, $infile, $outfile, $depth) = @_; DEBUG and print STDERR "Initting $self\n for $module\n", " in $infile\n out $outfile\n depth $depth\n"; $self->batch_mode(1); $self->batch_mode_current_level($depth); return $self; } sub run { my $self = $_[0]; return $self->do_middle if $self->bare_output; return $self->do_beginning && $self->do_middle && $self->do_end; } #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ sub do_beginning { my $self = $_[0]; my $title; if(defined $self->force_title) { $title = $self->force_title; DEBUG and print STDERR "Forcing title to be $title\n"; } else { # Actually try looking for the title in the document: $title = $self->get_short_title(); unless($self->content_seen) { DEBUG and print STDERR "No content seen in search for title.\n"; return; } $self->{'Title'} = $title; if(defined $title and $title =~ m/\S/) { $title = $self->title_prefix . esc($title) . $self->title_postfix; } else { $title = $self->default_title; $title = '' unless defined $title; DEBUG and print STDERR "Title defaults to $title\n"; } } my $after = $self->html_header_after_title || ''; if($self->html_css) { my $link = $self->html_css =~ m/html_css # It's a big blob of markup, let's drop it in : sprintf( # It's just a URL, so let's wrap it up qq[\n], $self->html_css, ); $after =~ s{()}{$link\n$1}i; # otherwise nevermind } $self->_add_top_anchor(\$after); if($self->html_javascript) { my $link = $self->html_javascript =~ m/html_javascript # It's a big blob of markup, let's drop it in : sprintf( # It's just a URL, so let's wrap it up qq[\n], $self->html_javascript, ); $after =~ s{()}{$link\n$1}i; # otherwise nevermind } print {$self->{'output_fh'}} $self->html_header_before_title || '', $title, # already escaped $after, ; DEBUG and print STDERR "Returning from do_beginning...\n"; return 1; } sub _add_top_anchor { my($self, $text_r) = @_; unless($$text_r and $$text_r =~ m/name=['"]___top['"]/) { # a hack $$text_r .= $self->top_anchor || ''; } return; } sub version_tag_comment { my $self = shift; return sprintf "\n", esc( ref($self), $self->VERSION(), $ISA[0], $ISA[0]->VERSION(), $], scalar(gmtime), ), $self->_modnote(), ; } sub _modnote { my $class = ref($_[0]) || $_[0]; return join "\n " => grep m/\S/, split "\n", qq{ If you want to change this HTML document, you probably shouldn't do that by changing it directly. Instead, see about changing the calling options to $class, and/or subclassing $class, then reconverting this document from the Pod source. When in doubt, email the author of $class for advice. See 'perldoc $class' for more info. }; } sub do_end { my $self = $_[0]; print {$self->{'output_fh'}} $self->html_footer || ''; return 1; } # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - # Normally this would just be a call to _do_middle_main_loop -- but we # have to do some elaborate things to emit all the content and then # summarize it and output it /before/ the content that it's a summary of. sub do_middle { my $self = $_[0]; return $self->_do_middle_main_loop unless $self->index; if( $self->output_string ) { # An efficiency hack my $out = $self->output_string; #it's a reference to it my $sneakytag = "\f\f\e\e\b\bIndex Here\e\e\b\b\f\f\n"; $$out .= $sneakytag; $self->_do_middle_main_loop; $sneakytag = quotemeta($sneakytag); my $index = $self->index_as_html(); if( $$out =~ s/$sneakytag/$index/s ) { # Expected case DEBUG and print STDERR "Inserted ", length($index), " bytes of index HTML into $out.\n"; } else { DEBUG and print STDERR "Odd, couldn't find where to insert the index in the output!\n"; # I don't think this should ever happen. } return 1; } unless( $self->output_fh ) { require Carp; Carp::confess("Parser object \$p doesn't seem to have any output object! I don't know how to deal with that."); } # If we get here, we're outputting to a FH. So we need to do some magic. # Namely, divert all content to a string, which we output after the index. my $fh = $self->output_fh; my $content = ''; { # Our horrible bait and switch: $self->output_string( \$content ); $self->_do_middle_main_loop; $self->abandon_output_string(); $self->output_fh($fh); } print $fh $self->index_as_html(); print $fh $content; return 1; } ########################################################################### sub index_as_html { my $self = $_[0]; # This is meant to be called AFTER the input document has been parsed! my $points = $self->{'PSHTML_index_points'} || []; @$points > 1 or return qq[
      \n]; # There's no point in having a 0-item or 1-item index, I dare say. my(@out) = qq{\n
      }; my $level = 0; my( $target_level, $previous_tagname, $tagname, $text, $anchorname, $indent); foreach my $p (@$points, ['head0', '(end)']) { ($tagname, $text) = @$p; $anchorname = $self->section_escape($text); if( $tagname =~ m{^head(\d+)$} ) { $target_level = 0 + $1; } else { # must be some kinda list item if($previous_tagname =~ m{^head\d+$} ) { $target_level = $level + 1; } else { $target_level = $level; # no change needed } } # Get to target_level by opening or closing ULs while($level > $target_level) { --$level; push @out, (" " x $level) . "
    "; } while($level < $target_level) { ++$level; push @out, (" " x ($level-1)) . "
  • I Same as \"I\" in current package. B<|>I Run debugger command, piping DB::OUT to current pager. B<||>I Same as B<|>I but DB::OUT is temporarily select()ed as well. B<\=> [I I] Define a command alias, or list current aliases. I Execute as a perl statement in current package. B Pure-man-restart of debugger, some of debugger state and command-line options may be lost. Currently the following settings are preserved: history, breakpoints and actions, debugger Bptions and the following command-line options: I<-w>, I<-I>, I<-e>. B [I] ... Set boolean option to true B [IB] Query options B [IB<=>I] [I=B<\">IB<\">] ... Set options. Use quotes if spaces in value. I, I chars used to recall command or spawn shell; I program for output of \"|cmd\"; I run Tk while prompting (with ReadLine); I I I level of verbosity; I Allows stepping off the end of the script. I Debugger should stop as early as possible. I Remote hostname:port for remote debugging The following options affect what happens with B, B, and B commands: I, I print only first N elements ('' for all); I, I change style of array and hash dump; I whether to print contents of globs; I dump arrays holding debugged files; I dump symbol tables of packages; I dump contents of \"reused\" addresses; I, I, I change style of string dump; I Do not print the overload-stringified value; Other options include: I affects printing of return value after B command, I affects printing messages on subroutine entry/exit. I affects printing messages on possible breaking points. I gives max length of evals/args listed in stack trace. I affects screen appearance of the command line. I bits control attempts to create a new TTY on events: 1: on fork() 2: debugger is started inside debugger 4: on startup During startup options are initialized from \$ENV{PERLDB_OPTS}. You can put additional initialization options I, I, I, I, and I there (or use B after you set them). B or B<^D> Quit. Set B<\$DB::finished = 0> to debug global destruction. B Summary of debugger commands. B [I] Get help [on a specific debugger command], enter B<|h> to page. B Long help for debugger commands B<$doccmd> I Runs the external doc viewer B<$doccmd> command on the named Perl I, or on B<$doccmd> itself if omitted. Set B<\$DB::doccmd> to change viewer. Type '|h h' for a paged display if this was too hard to read. "; # Fix balance of vi % matching: }}}} # note: tabs in the following section are not-so-helpful $summary = <<"END_SUM"; I I B [I|I] List source code B Stack trace B<-> or B<.> List previous/current line B [I] Single step [in expr] B [I] View around line B [I] Next, steps over subs B I View source in file /B> Repeat last B or B BIB BIB Search forw/backw B Return from subroutine B Show module versions B [I|I] Continue until position I B List break/watch/actions B [...] Set debugger options B [I] [I] Toggle trace [max depth] ][trace expr] B<<>[B<<>]|B<{>[B<{>]|B<>>[B<>>] [I] Do pre/post-prompt B [I|I|I] [I] Set breakpoint B<$prc> [I|I] Redo a previous command B I Delete a/all breakpoints B [I<-num>] Display last num commands B [I] I Do cmd before line B<=> [I I] Define/list an alias B I Delete a/all actions B [I] Get help on command B I Add a watch expression B Complete help page B I Delete a/all watch exprs B<|>[B<|>]I Send output to pager B<$psh>\[B<$psh>\] I Run cmd in a subprocess B or B<^D> Quit B Attempt a restart I B Execute perl code, also see: B,B,B I B|B I Evals expr in list context, dumps the result or lists methods. B