File Coverage

blib/lib/PLS/Parser/Pod.pm
Criterion Covered Total %
statement 59 143 41.2
branch 5 32 15.6
condition 5 25 20.0
subroutine 15 25 60.0
pod 12 14 85.7
total 96 239 40.1


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