File Coverage

lib/Pod/PerldocJp.pm
Criterion Covered Total %
statement 30 297 10.1
branch 0 206 0.0
condition 0 96 0.0
subroutine 10 25 40.0
pod 11 11 100.0
total 51 635 8.0


line stmt bran cond sub pod time code
1             package Pod::PerldocJp;
2              
3 1     1   87636 use strict;
  1         2  
  1         31  
4 1     1   4 use warnings;
  1         2  
  1         26  
5 1     1   4 use base 'Pod::Perldoc';
  1         2  
  1         830  
6 1     1   29422 use Encode;
  1         2  
  1         77  
7 1     1   792 use Encode::Guess;
  1         4259  
  1         3  
8 1     1   668 use Term::Encoding;
  1         662  
  1         50  
9 1     1   717 use HTTP::Tiny;
  1         52269  
  1         44  
10 1     1   913 use Path::Tiny;
  1         11787  
  1         73  
11 1     1   597 use URI::Escape;
  1         1485  
  1         74  
12 1     1   645 use utf8;
  1         15  
  1         5  
13              
14             my $term_encoding = Term::Encoding::get_encoding() || 'utf-8';
15              
16             our $VERSION = '0.20';
17              
18 0     0 1   sub opt_J { shift->_elem('opt_J', @_) }
19              
20             sub _perldocjp_dir {
21 0     0     my $self = shift;
22              
23             my @subs = (
24             sub {
25 0     0     require File::HomeDir;
26 0           path(File::HomeDir->my_home, '.perldocjp');
27             },
28 0     0     sub { path(File::Spec->tmpdir, '.perldocjp') },
29 0     0     sub { path('.') },
30 0           );
31              
32 0           foreach my $sub (@subs) {
33 0 0         my $dir = eval { $sub->() } or next;
  0            
34 0           $dir->mkpath;
35 0 0 0       return $dir if -d $dir && -w $dir;
36             };
37             }
38              
39             sub grand_search_init {
40 0     0 1   my ($self, $pages, @found) = @_;
41              
42 0 0         my $dir = $self->_perldocjp_dir()
43             or return $self->SUPER::grand_search_init($pages, @found);
44              
45             my @encodings =
46 0   0       split ' ', $ENV{PERLDOCJP_ENCODINGS} || 'euc-jp shiftjis utf8';
47              
48 0 0 0       if (not $self->opt_F and ($self->opt_J or ($pages->[0] && $pages->[0] =~ /^https?:/))) {
      0        
49 0           my $ua = HTTP::Tiny->new(agent => "Pod-PerldocJp/$VERSION");
50              
51 0   0       my $api_url = $ENV{PERLDOCJP_SERVER} || 'https://perldoc.jp';
52 0           $api_url =~ s|/+$||;
53              
54 0           foreach my $page (@$pages) {
55 0           $self->aside("Searching for $page\n");
56 0 0         my $url = ($page =~ /^https?:/) ? $page : "$api_url/$page";
57 0           my $file = $dir->child(uri_escape($page, '^A-Za-z0-9_') . '.pod');
58 0 0 0       unless ($file->exists && $file->stat->size && $file->stat->mtime > time - 60 * 60 * 24) {
      0        
59 0           my $res = $ua->get($url);
60 0 0         unless ($res->{success}) {
61 0           next;
62             }
63             # perldoc.jp is doing redirects.
64             # For example, if you access https://perldoc.jp/perlfunc, you will be redirected to http://perldoc.jp/docs/perl/5.26.1/perlfunc.pod.
65 0 0         if ($res->{redirects}) {
66             # perldoc.jp xxx.pod page is rendering pod to html.
67             # xxx.pod.pod page is natural pod page.
68 0           $url = $res->{redirects}->[-1]->{headers}->{location} . '.pod';
69 0           $res = $ua->get($url);
70 0 0         next unless ($res->{success});
71             }
72 0           my $pod = $res->{content};
73 0 0         if ($pod !~ /^=encoding\s/m) {
74             # You can't trust perldoc.jp's Content-Type too much.
75             # (there're several utf-8 translations, though perldoc.jp
76             # is (or was) supposed to use euc-jp)
77 0           my $encoding;
78 0           my $enc = guess_encoding($pod, @encodings);
79 0 0         if (ref $enc) {
    0          
80 0           $encoding = $enc->name;
81             }
82             elsif (my $ctype = $res->{headers}{'content-type'}) {
83 0           ($encoding) = $ctype =~ /charset\s*=\s*([\w-]+)/;
84             }
85 0 0         if ($encoding) {
86 0           $pod = "=encoding $encoding\n\n$pod";
87             }
88             }
89 0           $file->spew($pod);
90             }
91 0 0         push @found, "$file" if $file->stat->size;
92             }
93 0 0         return @found if @found;
94             }
95              
96 0           @found = $self->SUPER::grand_search_init($pages, @found);
97              
98 0 0         if ($self->opt_J) {
99 0           foreach my $path (@found) {
100 0           my $pod = path($path)->slurp;
101 0 0         unless ($pod =~ /^=encoding\s/m) {
102 0           my $encoding;
103 0           my $enc = guess_encoding($pod, @encodings);
104 0 0         if (ref $enc) {
105 0           $encoding = $enc->name;
106 0 0         next if $encoding eq 'ascii';
107 0           $pod = "=encoding $encoding\n\n$pod";
108 0           my $file = $dir->child(uri_escape($path, '^A-Za-z0-9_'));
109 0           $file->spew($pod);
110 0 0         $path = "$file" if $file->stat->size;
111             }
112             }
113             }
114             }
115 0           @found;
116             }
117              
118             {
119             # shamelessly ripped from Pod::Perldoc 3.23 and tweaked
120              
121             sub opt_o_with { # "o" for output format
122 0     0 1   my($self, $rest) = @_;
123 0 0 0       return unless defined $rest and length $rest;
124 0 0         if($rest =~ m/^(\w+)$/s) {
125 0           $rest = $1; #untaint
126             } else {
127 0           $self->warn( qq("$rest" isn't a valid output format. Skipping.\n") );
128 0           return;
129             }
130              
131 0           $self->aside("Noting \"$rest\" as desired output format...\n");
132              
133             # Figure out what class(es) that could actually mean...
134              
135 0           my @classes;
136             # TWEAKED: to include "Pod::PerldocJp::To"
137 0           foreach my $prefix ("Pod::PerldocJp::To", "Pod::Perldoc::To", "Pod::Simple::", "Pod::") {
138             # Messy but smart:
139 0           foreach my $stem (
140             $rest, # Yes, try it first with the given capitalization
141             "\L$rest", "\L\u$rest", "\U$rest" # And then try variations
142              
143             ) {
144 0           $self->aside("Considering $prefix$stem\n");
145 0           push @classes, $prefix . $stem;
146             }
147              
148             # Tidier, but misses too much:
149             #push @classes, $prefix . ucfirst(lc($rest));
150             }
151 0           $self->opt_M_with( join ";", @classes );
152 0           return;
153             }
154              
155             sub init_formatter_class_list {
156 0     0 1   my $self = shift;
157 0   0       $self->{'formatter_classes'} ||= [];
158              
159             # Remember, no switches have been read yet, when
160             # we've started this routine.
161              
162 0           $self->opt_M_with('Pod::Perldoc::ToPod'); # the always-there fallthru
163 0           $self->opt_o_with('text');
164              
165             # TWEAKED: XXX: should support term later
166             # $self->opt_o_with('term') unless $self->is_mswin32 || $self->is_dos
167             # || !($ENV{TERM} && (
168             # ($ENV{TERM} || '') !~ /dumb|emacs|none|unknown/i
169             # ));
170              
171 0           return;
172             }
173              
174             sub maybe_generate_dynamic_pod {
175 0     0 1   my ($self, $found_things) = @_;
176 0           my @dynamic_pod;
177              
178 0 0         $self->search_perlapi($found_things, \@dynamic_pod) if $self->opt_a;
179              
180 0 0         $self->search_perlfunc($found_things, \@dynamic_pod) if $self->opt_f;
181              
182 0 0         $self->search_perlvar($found_things, \@dynamic_pod) if $self->opt_v;
183              
184 0 0         $self->search_perlfaqs($found_things, \@dynamic_pod) if $self->opt_q;
185              
186 0 0 0       if( ! $self->opt_f and ! $self->opt_q and ! $self->opt_v and ! $self->opt_a) {
    0          
187 0           Pod::Perldoc::DEBUG > 4 and print "That's a non-dynamic pod search.\n";
188             } elsif ( @dynamic_pod ) {
189 0           $self->aside("Hm, I found some Pod from that search!\n");
190 0           my ($buffd, $buffer) = $self->new_tempfile('pod', 'dyn');
191 0 0 0       if ( $] >= 5.008 && $self->opt_L ) {
192 0           binmode($buffd, ":utf8");
193 0           print $buffd "=encoding utf8\n\n";
194             }
195              
196 0           push @{ $self->{'temp_file_list'} }, $buffer;
  0            
197             # I.e., it MIGHT be deleted at the end.
198              
199 0   0       my $in_list = !$self->not_dynamic && $self->opt_f || $self->opt_v || $self->opt_a;
200             # TWEAKED: to add =encoding utf-8 and encode_utf8
201 0           print $buffd "=encoding utf-8\n\n";
202 0 0         print $buffd "=over 8\n\n" if $in_list;
203 0 0         print $buffd map {encode_utf8($_)} @dynamic_pod or die "Can't print $buffer: $!";
  0            
204 0 0         print $buffd "=back\n" if $in_list;
205              
206 0 0         close $buffd or $self->die( "Can't close $buffer: $!" );
207              
208 0           @$found_things = $buffer;
209             # Yes, so found_things never has more than one thing in
210             # it, by time we leave here
211              
212 0           $self->add_formatter_option('__filter_nroff' => 1);
213              
214             } else {
215 0           @$found_things = ();
216 0           $self->aside("I found no Pod from that search!\n");
217             }
218              
219 0           return;
220             }
221              
222             sub search_perlfunc {
223 0     0 1   my($self, $found_things, $pod) = @_;
224              
225 0           Pod::Perldoc::DEBUG > 2 and print "Search: @$found_things\n";
226              
227 0           my $perlfunc = shift @$found_things;
228 0 0         open(PFUNC, "<", $perlfunc) # "Funk is its own reward"
229             or $self->die("Can't open $perlfunc: $!");
230              
231             # Functions like -r, -e, etc. are listed under `-X'.
232 0 0         my $search_re = ($self->opt_f =~ /^-[rwxoRWXOeszfdlpSbctugkTBMAC]$/)
233             ? '(?:I<)?-X' : quotemeta($self->opt_f) ;
234              
235 0           Pod::Perldoc::DEBUG > 2 and
236             print "Going to perlfunc-scan for $search_re in $perlfunc\n";
237              
238 0           my $re = 'Alphabetical Listing of Perl Functions';
239              
240             # Check available translator or backup to default (english)
241 0 0 0       if ( $self->opt_L && defined $self->{'translators'}->[0] ) {
242 0           my $tr = $self->{'translators'}->[0];
243 0 0         $re = $tr->search_perlfunc_re if $tr->can('search_perlfunc_re');
244 0 0         if ( $] < 5.008 ) {
245 0           $self->aside("Your old perl doesn't really have proper unicode support.");
246             }
247             else {
248 0           binmode(PFUNC, ":utf8");
249             }
250             }
251              
252             # Skip introduction
253 0           local $_;
254             # TWEAKED: to find encoding
255 0           my $encoding = 'utf-8';
256 0           while () {
257 0 0         if (/^=encoding\s+(\S+)/) {
258 0           $encoding = $1;
259             }
260 0 0         last if /^=head2 $re/;
261             }
262              
263             # Look for our function
264 0           my $found = 0;
265 0           my $inlist = 0;
266              
267 0           my @perlops = qw(m q qq qr qx qw s tr y);
268              
269 0           my @related;
270             my $related_re;
271 0           while () { # "The Mothership Connection is here!"
272 0 0         last if( grep{ $self->opt_f eq $_ }@perlops );
  0            
273              
274 0 0 0       if ( /^=over/ and not $found ) {
    0 0        
      0        
275 0           ++$inlist;
276             }
277             elsif ( /^=back/ and not $found and $inlist ) {
278 0           --$inlist;
279             }
280              
281              
282 0 0 0       if ( m/^=item\s+$search_re\b/ and $inlist < 2 ) {
    0 0        
    0 0        
    0          
283 0           $found = 1;
284             }
285             elsif (@related > 1 and /^=item/) {
286 0   0       $related_re ||= join "|", @related;
287 0 0         if (m/^=item\s+(?:$related_re)\b/) {
288 0           $found = 1;
289             }
290             else {
291 0 0 0       last if $found > 1 and $inlist < 2;
292             }
293             }
294             elsif (/^=item/) {
295 0 0 0       last if $found > 1 and $inlist < 2;
296             }
297             elsif ($found and /^X<[^>]+>/) {
298 0           push @related, m/X<([^>]+)>/g;
299             }
300 0 0         next unless $found;
301 0 0         if (/^=over/) {
    0          
302 0           ++$inlist;
303             }
304             elsif (/^=back/) {
305 0           --$inlist;
306             }
307             # TWEAKED: to decode
308 0           push @$pod, decode($encoding, $_);
309 0 0         ++$found if /^\w/; # found descriptive text
310             }
311              
312 0 0         if( !@$pod ){
313 0           $self->search_perlop( $found_things, $pod );
314             }
315              
316 0 0         if (!@$pod) {
317 0           CORE::die( sprintf
318             "No documentation for perl function `%s' found\n",
319             $self->opt_f )
320             ;
321             }
322 0 0         close PFUNC or $self->die( "Can't open $perlfunc: $!" );
323              
324 0           return;
325             }
326              
327             sub search_perlvar {
328 0     0 1   my ($self, $found_things, $pod) = @_;
329              
330 0           my $opt = $self->opt_v;
331              
332 0 0         if ( $opt !~ /^ (?: [\@\%\$]\S+ | [A-Z]\w* ) $/x ) {
333 0           CORE::die( "'$opt' does not look like a Perl variable\n" );
334             }
335              
336 0           Pod::Perldoc::DEBUG > 2 and print "Search: @$found_things\n";
337              
338 0           my $perlvar = shift @$found_things;
339 0 0         open(PVAR, "<", $perlvar) # "Funk is its own reward"
340             or $self->die("Can't open $perlvar: $!");
341              
342 0 0 0       if ( $opt ne '$0' && $opt =~ /^\$\d+$/ ) { # handle $1, $2, ..., $9
343 0           $opt = '$>';
344             }
345 0           my $search_re = quotemeta($opt);
346              
347 0           Pod::Perldoc::DEBUG > 2 and
348             print "Going to perlvar-scan for $search_re in $perlvar\n";
349              
350             # Skip introduction
351 0           local $_;
352             # TWEAKED: to find encoding
353 0           my $encoding = 'utf-8';
354 0           while () {
355 0 0         if (/^=encoding\s+(\S+)/) {
356 0           $encoding = $1;
357             }
358 0 0         last if /^=over 8/;
359             }
360              
361             # Look for our variable
362 0           my $found = 0;
363 0           my $inheader = 1;
364 0           my $inlist = 0;
365 0           while () { # "The Mothership Connection is here!"
366 0 0         last if /^=head2 Error Indicators/;
367             # \b at the end of $` and friends borks things!
368 0 0         if ( m/^=item\s+$search_re\s/ ) {
    0          
    0          
369 0           $found = 1;
370             }
371             elsif (/^=item/) {
372 0 0 0       last if $found && !$inheader && !$inlist;
      0        
373             }
374             elsif (!/^\s+$/) { # not a blank line
375 0 0         if ( $found ) {
376 0           $inheader = 0; # don't accept more =item (unless inlist)
377             }
378             else {
379 0           @$pod = (); # reset
380 0           $inheader = 1; # start over
381 0           next;
382             }
383             }
384              
385 0 0         if (/^=over/) {
    0          
386 0           ++$inlist;
387             }
388             elsif (/^=back/) {
389 0 0 0       last if $found && !$inheader && !$inlist;
      0        
390 0           --$inlist;
391             }
392             # TWEAKED: to decode
393 0           push @$pod, decode($encoding, $_);
394             # ++$found if /^\w/; # found descriptive text
395             }
396 0 0         @$pod = () unless $found;
397 0 0         if (!@$pod) {
398 0           CORE::die( "No documentation for perl variable '$opt' found\n" );
399             }
400 0 0         close PVAR or $self->die( "Can't open $perlvar: $!" );
401              
402 0           return;
403             }
404              
405             sub search_perlfaqs {
406 0     0 1   my ($self, $found_things, $pod) = @_;
407              
408 0           my $found = 0;
409 0           my %found_in;
410 0           my $search_key = $self->opt_q;
411              
412 0 0         my $rx = eval { qr/$search_key/ }
  0            
413             or $self->die( <
414             Invalid regular expression '$search_key' given as -q pattern:
415             $@
416             Did you mean \\Q$search_key ?
417              
418             EOD
419              
420 0           local $_;
421 0           foreach my $file (@$found_things) {
422 0 0         $self->die( "invalid file spec: $!" ) if $file =~ /[<>|]/;
423 0 0         open(INFAQ, "<", $file) # XXX 5.6ism
424             or $self->die( "Can't read-open $file: $!\nAborting" );
425             # TWEAKED: to find encoding
426 0           my $encoding = 'utf-8';
427 0           while () {
428 0 0         if (/^=encoding\s+(\S+)/) {
429 0           $encoding = $1;
430             }
431 0 0         if ( m/^=head2\s+.*(?:$search_key)/i ) {
    0          
432 0           $found = 1;
433 0 0         push @$pod, "=head1 Found in $file\n\n" unless $found_in{$file}++;
434             }
435             elsif (/^=head[12]/) {
436 0           $found = 0;
437             }
438 0 0         next unless $found;
439             # TWEAKED: to decode
440 0           push @$pod, decode($encoding, $_);
441             }
442 0           close(INFAQ);
443             }
444 0 0         CORE::die("No documentation for perl FAQ keyword `$search_key' found\n")
445             unless @$pod;
446              
447 0 0         if ( $self->opt_l ) {
448 0           CORE::die((join "\n", keys %found_in) . "\n");
449             }
450 0           return;
451             }
452              
453             sub search_perlapi {
454 0     0 1   my($self, $found_things, $pod) = @_;
455              
456 0           Pod::Perldoc::DEBUG > 2 and print "Search: @$found_things\n";
457              
458 0           my $perlapi = shift @$found_things;
459 0 0         open(PAPI, "<", $perlapi) # "Funk is its own reward"
460             or $self->die("Can't open $perlapi: $!");
461              
462 0           my $search_re = quotemeta($self->opt_a);
463              
464 0           Pod::Perldoc::DEBUG > 2 and
465             print "Going to perlapi-scan for $search_re in $perlapi\n";
466              
467             # Check available translator or backup to default (english)
468 0 0 0       if ( $self->opt_L && defined $self->{'translators'}->[0] ) {
469 0           my $tr = $self->{'translators'}->[0];
470 0 0         if ( $] < 5.008 ) {
471 0           $self->aside("Your old perl doesn't really have proper unicode support.");
472             }
473             else {
474 0           binmode(PAPI, ":utf8");
475             }
476             }
477              
478 0           local $_;
479             # TWEAKED: to find encoding
480 0           my $encoding = 'utf-8';
481 0           while () {
482 0 0         if (/^=encoding\s+(\S+)/) {
483 0           $encoding = $1;
484             }
485 0 0         last if /^=over 8/;
486             }
487              
488             # Look for our function
489 0           my $found = 0;
490 0           my $inlist = 0;
491              
492 0           my @related;
493             my $related_re;
494 0           while () { # "The Mothership Connection is here!"
495 0 0 0       if ( m/^=item\s+$search_re\b/ ) {
    0 0        
    0          
    0          
496 0           $found = 1;
497             }
498             elsif (@related > 1 and /^=item/) {
499 0   0       $related_re ||= join "|", @related;
500 0 0         if (m/^=item\s+(?:$related_re)\b/) {
501 0           $found = 1;
502             }
503             else {
504 0           last;
505             }
506             }
507             elsif (/^=item/) {
508 0 0 0       last if $found > 1 and not $inlist;
509             }
510             elsif ($found and /^X<[^>]+>/) {
511 0           push @related, m/X<([^>]+)>/g;
512             }
513 0 0         next unless $found;
514 0 0         if (/^=over/) {
    0          
515 0           ++$inlist;
516             }
517             elsif (/^=back/) {
518 0 0 0       last if $found > 1 and not $inlist;
519 0           --$inlist;
520             }
521 0           push @$pod, decode($encoding, $_);
522 0 0         ++$found if /^\w/; # found descriptive text
523             }
524              
525 0 0         if (!@$pod) {
526 0           CORE::die( sprintf
527             "No documentation for perl api function '%s' found\n",
528             $self->opt_a )
529             ;
530             }
531 0 0         close PAPI or $self->die( "Can't open $perlapi: $!" );
532              
533 0           return;
534             }
535              
536             # TWEAKED: translation and encoding
537             sub usage {
538 0     0 1   my $self = shift;
539 0 0         $self->warn( "@_\n" ) if @_;
540              
541             # Erase evidence of previous errors (if any), so exit status is simple.
542 0           $! = 0;
543              
544 0           my $usage = <<"EOF";
545             perldoc [options] PageName|ModuleName|ProgramName|URL...
546             perldoc [options] -f BuiltinFunction
547             perldoc [options] -q FAQRegex
548             perldoc [options] -v PerlVariable
549              
550             オプション:
551             -h このヘルプを表示する
552             -V バージョンを表示する
553             -r 再帰検索 (時間がかかります)
554             -i 大文字小文字を無視する
555             -t pod2manとnroffではなくpod2textを使って表示(デフォルト)
556             -u 整形前のPODを表示する
557             -m 指定したモジュールのコードも含めて表示する
558             -n nroffのかわりを指定する
559             -l モジュールのファイル名を表示する
560             -F 引数はモジュール名ではなくファイル名である
561             -D デバッグメッセージを表示する
562             -T ページャを通さずに画面に出力する
563             -d 保存するファイル名
564             -o 出力フォーマット名
565             -M フォーマット用のモジュール名(FormatterModuleNameToUse)
566             -w フォーマット用のオプション:値(formatter_option:option_value)
567             -L 国別コード。(あれば)翻訳を表示します
568             -X あれば索引を利用する (pod.idxを探します)
569             -J perldoc.jpの日本語訳も検索
570             -q perlfaq[1-9]の質問を検索
571             -f Perlの組み込み関数を検索
572             -a Perl APIを検索
573             -v Perlの定義済み変数を検索
574              
575             PageName|ModuleName|ProgramName|URL...
576             表示したいドキュメント名です。「perlfunc」のようなページ名、
577             モジュール名(「Term::Info」または「Term/Info」)、「perldoc」
578             のようなプログラム名、http(s)で始まるURLを指定できます。
579              
580             BuiltinFunction
581             Perlの関数名です。「perlfunc」ないし「perlop」からドキュメント
582             を抽出します。
583              
584             FAQRegex
585             正規表現です。perlfaq[1-9]を検索してマッチした質問を抽出します。
586              
587             PERLDOC環境変数で指定したスイッチはコマンドライン引数の前に適用されます。
588             PODの索引には(あれば)ファイル名の一覧が(1行に1つ)含まれています。
589              
590             [PerldocJp v$Pod::PerldocJp::VERSION based on Perldoc v$Pod::Perldoc::VERSION]
591             EOF
592              
593 0           CORE::die encode($term_encoding => $usage);
594             }
595              
596             sub usage_brief {
597 0     0 1   my $self = shift;
598 0           my $program_name = $self->program_name;
599              
600 0           my $usage =<<"EOUSAGE";
601             使い方: $program_name [-hVriDtumFXlTJ] [-n nroffer_program]
602             [-d output_filename] [-o output_format] [-M FormatterModule]
603             [-w formatter_option:option_value] [-L translation_code]
604             PageName|ModuleName|ProgramName
605              
606             Examples:
607              
608             $program_name -f PerlFunc
609             $program_name -q FAQKeywords
610             $program_name -v PerlVar
611             $program_name -a PerlAPI
612              
613             -hオプションをつけるともう少し詳しいヘルプが表示されます。
614             詳細は"perldocjp perldocjp"をご覧ください。
615             [PerldocJp v$Pod::PerldocJp::VERSION based on Perldoc v$Pod::Perldoc::VERSION]
616             EOUSAGE
617              
618 0           CORE::die encode($term_encoding => $usage);
619             }
620             }
621              
622             1;
623              
624             __END__