File Coverage

blib/lib/PLS/Parser/Pod.pm
Criterion Covered Total %
statement 47 134 35.0
branch 3 32 9.3
condition 0 18 0.0
subroutine 13 24 54.1
pod 12 14 85.7
total 75 222 33.7


line stmt bran cond sub pod time code
1              
2             use strict;
3 11     11   3589 use warnings;
  11         22  
  11         239  
4 11     11   36  
  11         20  
  11         191  
5             use File::Spec;
6 11     11   43 use FindBin;
  11         20  
  11         310  
7 11     11   444 use IPC::Open3;
  11         891  
  11         336  
8 11     11   4076 use Pod::Markdown;
  11         23018  
  11         567  
9 11     11   4785 use Pod::Simple::Search;
  11         316528  
  11         331  
10 11     11   4154 use Symbol qw(gensym);
  11         41750  
  11         334  
11 11     11   58  
  11         22  
  11         455  
12             use PLS::Parser::Index;
13 11     11   53 use PLS::Server::State;
  11         13  
  11         163  
14 11     11   3613  
  11         31  
  11         9984  
15             =head1 NAME
16              
17             PLS::Parser::Pod
18              
19             =head1 DESCRIPTION
20              
21             This class finds and parses POD for an element. It formats the POD into markdown suitable
22             for sending to the Language Server Protocol.
23              
24             =cut
25              
26             my $PERL_EXE = $^X;
27             my $PERL_ARGS = [];
28              
29             {
30             my ($class, @args) = @_;
31              
32 0     0 0 0 my %args = @args;
33              
34 0         0 my %self = (
35             index => $args{index},
36             element => $args{element}
37             );
38              
39 0         0 return bless \%self, $class;
40             } ## end sub new
41 0         0  
42             =head2 set_perl_exe
43              
44             Store the perl executable path.
45              
46             =cut
47              
48             {
49             my (undef, $perl_exe) = @_;
50              
51             $PERL_EXE = $perl_exe if (length $perl_exe and -x $perl_exe);
52 0     0 1 0  
53             return;
54 0 0 0     0 } ## end sub set_perl_exe
55              
56 0         0 =head2 get_perl_exe
57              
58             Get the perl executable path.
59              
60             =cut
61              
62             {
63             return $PERL_EXE;
64             }
65              
66             =head2 set_perl_args
67 10     10 1 50  
68             Set the arguments to be used when using the perl binary.
69              
70             =cut
71              
72             {
73             my (undef, $args) = @_;
74              
75             $PERL_ARGS = $args;
76             }
77              
78 0     0 1 0 =head2 get_perl_args
79              
80 0         0 Get the arguments to be used when using the perl binary.
81              
82             =cut
83              
84             {
85             return $PERL_ARGS;
86             }
87              
88             =head2 get_perldoc_location
89              
90             Tries to find the path to the perldoc utility.
91 10     10 1 67  
92             =cut
93              
94             {
95             my (undef, $dir) = File::Spec->splitpath($^X);
96             my $perldoc = File::Spec->catfile($dir, 'perldoc');
97              
98             # try to use the perldoc matching this perl executable, falling back to the perldoc in the PATH
99             return (-f $perldoc and -x $perldoc) ? $perldoc : 'perldoc';
100             } ## end sub get_perldoc_location
101              
102 0     0 1 0 =head2 run_perldoc_command
103 0         0  
104             Runs a perldoc command and returns the text formatted into markdown.
105              
106 0 0 0     0 =cut
107              
108             {
109             my ($class, @command) = @_;
110              
111             my $markdown = '';
112              
113             my $err = gensym;
114             my $pid = open3(my $in, my $out, $err, get_perldoc_location(), @command);
115              
116             close $in, () = <$err>; # need to read all of error file handle
117 0     0 1 0 my $pod = do { local $/; <$out> };
118             close $out;
119 0         0 waitpid $pid, 0;
120             my $exit_code = $? >> 8;
121 0         0 return 0 if $exit_code != 0;
122 0         0 return $class->get_markdown_from_text(\$pod);
123             } ## end sub run_perldoc_command
124 0         0  
125 0         0 =head2 get_markdown_for_package
  0         0  
  0         0  
126 0         0  
127 0         0 Finds the POD for a package and returns its POD, formatted into markdown.
128 0         0  
129 0 0       0 =cut
130 0         0  
131             {
132             my ($class, $package) = @_;
133              
134             my $include = $class->get_clean_inc();
135             my $search = Pod::Simple::Search->new();
136             $search->inc(0);
137             my $path = $search->find($package, @{$include});
138             return unless (length $path);
139             open my $fh, '<', $path or return;
140             my $text = do { local $/; <$fh> };
141 0     0 1 0 return $class->get_markdown_from_text(\$text);
142             } ## end sub get_markdown_for_package
143 0         0  
144 0         0 =head2 get_markdown_from_lines
145 0         0  
146 0         0 This formats POD from an array of lines into markdown and fixes up improperly formatted text.
  0         0  
147 0 0       0  
148 0 0       0 =cut
149 0         0  
  0         0  
  0         0  
150 0         0 {
151             my ($class, $lines) = @_;
152              
153             my $markdown = '';
154             my $parser = Pod::Markdown->new();
155              
156             $parser->output_string(\$markdown);
157             $parser->no_whining(1);
158             $parser->parse_lines(@$lines, undef);
159              
160             $class->clean_markdown(\$markdown);
161 0     0 1 0  
162             my $ok = $parser->content_seen;
163 0         0 return 0 unless $ok;
164 0         0 return $ok, \$markdown;
165             } ## end sub get_markdown_from_lines
166 0         0  
167 0         0 =head2 get_markdown_from_text
168 0         0  
169             This formats POD from SCALAR ref to a string into markdown and fixes up improperly formatted text.
170 0         0  
171             =cut
172 0         0  
173 0 0       0 {
174 0         0 my ($class, $text) = @_;
175              
176             my $markdown = '';
177             my $parser = Pod::Markdown->new();
178              
179             $parser->output_string(\$markdown);
180             $parser->no_whining(1);
181             $parser->parse_string_document($$text);
182              
183             $class->clean_markdown(\$markdown);
184              
185 0     0 1 0 my $ok = $parser->content_seen;
186             return 0 unless $ok;
187 0         0 return $ok, \$markdown;
188 0         0 } ## end sub get_markdown_from_text
189              
190 0         0 {
191 0         0 my ($self, $path, $name) = @_;
192 0         0  
193             open my $fh, '<', $path or return 0;
194 0         0  
195             my @lines;
196 0         0 my $start = '';
197 0 0       0  
198 0         0 while (my $line = <$fh>)
199             {
200             if ($line =~ /^=(head\d|item).*\b\Q$name\E\b.*$/)
201             {
202             $start = $1;
203 0     0 0 0 push @lines, $line;
204             next;
205 0 0       0 } ## end if ($line =~ /^=(head\d|item).*\b\Q$name\E\b.*$/...)
206              
207 0         0 if (length $start)
208 0         0 {
209             push @lines, $line;
210 0         0  
211             if ( $start eq 'item' and $line =~ /^=item/
212 0 0       0 or $start =~ /head/ and $line =~ /^=$start/
213             or $line =~ /^=cut/)
214 0         0 {
215 0         0 last;
216 0         0 } ## end if ($start eq 'item' and...)
217             } ## end if (length $start)
218             } ## end while (my $line = <$fh>)
219 0 0       0  
220             close $fh;
221 0         0  
222             # we don't want the last line - it's a start of a new section.
223 0 0 0     0 pop @lines;
      0        
      0        
      0        
224              
225             my $markdown = '';
226              
227 0         0 if (scalar @lines)
228             {
229             my $parser = Pod::Markdown->new();
230              
231             $parser->output_string(\$markdown);
232 0         0 $parser->no_whining(1);
233             $parser->parse_lines(@lines, undef);
234              
235 0         0 # remove first extra space to avoid markdown from being displayed inappropriately as code
236             $markdown =~ s/\n\n/\n/;
237 0         0 my $ok = $parser->content_seen;
238             return 0 unless $ok;
239 0 0       0 return $ok, \$markdown;
240             } ## end if (scalar @lines)
241 0         0  
242             return 0;
243 0         0 } ## end sub find_pod_in_file
244 0         0  
245 0         0 =head2 clean_markdown
246              
247             This fixes markdown so that documentation isn't incorrectly displayed as code.
248 0         0  
249 0         0 =cut
250 0 0       0  
251 0         0 {
252             my ($class, $markdown) = @_;
253              
254 0         0 # remove first extra space to avoid markdown from being displayed inappropriately as code
255             $$markdown =~ s/\n\n/\n/;
256             } ## end sub clean_markdown
257              
258             =head2 combine_markdown
259              
260             This combines multiple markdown sections into a single string.
261              
262             =cut
263              
264             {
265 0     0 1 0 my ($class, @markdown_parts) = @_;
266              
267             return join "\n---\n", @markdown_parts;
268 0         0 }
269              
270             =head2 get_clean_inc
271              
272             Starts a new perl process and retrieves its @INC, so we do not use an @INC tainted
273             with things included in PLS.
274              
275             =cut
276              
277             {
278             # default to including everything except PLS code in search.
279 0     0 1 0 my @include = grep { not /\Q$FindBin::RealBin\E/ } @INC;
280              
281 0         0 # try to get a clean @INC from the perl we're using
282             if (my $pid = open my $perl, '-|', $PERL_EXE, '-e', q{$, = "\n"; print @INC; print "\n"})
283             {
284             @include = ();
285              
286             while (my $line = <$perl>)
287             {
288             chomp $line;
289             next unless (length $line);
290             push @include, $line;
291             } ## end while (my $line = <$perl>...)
292              
293             waitpid $pid, 0;
294 2     2 1 8 } ## end if (my $pid = open my ...)
  20         245  
295              
296             push @include, @{$PLS::Server::State::CONFIG->{inc}} if (ref $PLS::Server::State::CONFIG->{inc} eq 'ARRAY');
297 2 50       16772 my $index = PLS::Parser::Index->new();
298             push @include, @{PLS::Parser::Index->new->workspace_folders};
299 2         79  
300             return \@include;
301 2         2911 } ## end sub get_clean_inc
302              
303 14         44 1;