File Coverage

blib/lib/PLS/Parser/Pod.pm
Criterion Covered Total %
statement 65 148 43.9
branch 4 30 13.3
condition 5 25 20.0
subroutine 18 29 62.0
pod 12 14 85.7
total 104 246 42.2


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