File Coverage

blib/lib/Pod/Server.pm
Criterion Covered Total %
statement 7 9 77.7
branch n/a
condition n/a
subroutine 3 3 100.0
pod n/a
total 10 12 83.3


line stmt bran cond sub pod time code
1             package Pod::Server;
2 1     1   49994 use strict;
  1         3  
  1         46  
3 1     1   6 use warnings;
  1         2  
  1         31  
4 1     1   761 use Squatting;
  0            
  0            
5             use File::Which;
6             our $VERSION = '1.14';
7             $| = 1;
8              
9             my $vim = which('vim');
10              
11             our %CONFIG = (
12             background_color => '#112',
13             foreground_color => 'wheat',
14             pre_background_color => '#000',
15             pre_foreground_color => '#ccd',
16             code_foreground_color => '#fff',
17             a_foreground_color => '#fc4',
18             a_hover_foreground_color => '#fe8',
19             font_size => '10pt',
20             sidebar => 'right',
21             first => 'Squatting',
22             title => '#',
23             tree => [],
24             vim => $vim,
25             vim_comment => '#0cf',
26             vim_constant => '#0fc',
27             vim_identifier => '#0aa',
28             vim_statement => '#fc2',
29             vim_preproc => '#8fc',
30             vim_type => '#2e8b57',
31             vim_special => '#6a5acd',
32             vim_underlined => '#fff',
33             vim_error_bg => '#f00',
34             vim_error_fg => '#fff',
35             vim_todo_bg => '#fc2',
36             vim_todo_fg => '#222',
37             );
38              
39             sub init {
40             my $app = shift;
41             Pod::Server::Controllers::scan();
42             my $have_vim = eval { require Text::VimColor };
43             if (not $have_vim) {
44             $CONFIG{vim} = undef;
45             }
46             $app->next::method;
47             }
48              
49             package Pod::Server::Controllers;
50             use strict;
51             use warnings;
52             use File::Basename;
53             use File::Find;
54             use File::Which;
55             use Config;
56             use aliased 'Pod::Simple::Search';
57             use aliased 'Squatting::H';
58              
59             # skip files we've already seen
60             my %already_seen;
61              
62             # figure out where all(?) our pod is located
63             # (loosely based on zsh's _perl_basepods and _perl_modules)
64             our %perl_basepods;
65              
66             our %perl_programs;
67             our @perl_programs;
68              
69             our %perl_modules;
70             our @perl_modules;
71             sub scan {
72             no warnings;
73             warn "scanning for POD...\n";
74              
75             if ($Config{man1ext} ne "1") {
76             %perl_programs = map {
77             my ($file, $path, $suffix) = fileparse($_, qr/\.$Config{man1ext}.*$/);
78             $already_seen{$_} = 1;
79             ("$file" => which($file) || $_);
80             } (
81             glob("$Config{installman1dir}/*.$Config{man1ext}*"),
82             glob("$Config{installsiteman1dir}/*.$Config{man1ext}*"),
83             glob("$Config{installvendorman1dir}/*.$Config{man1ext}*")
84             );
85             }
86              
87             my $search = Search->new;
88             $search->limit_glob('*');
89             $search->progress(H->new({
90             reach => sub {
91             print ".";
92             },
93             done => sub {
94             print "\n";
95             },
96             }));
97              
98             my $survey;
99             if (scalar(@{$CONFIG{tree}})) {
100             $search->inc(0);
101             $survey = $search->survey(@{$CONFIG{tree}});
102             }
103             else {
104             $survey = $search->survey;
105             }
106              
107             for (keys %$survey) {
108             my $key = $_;
109             $key =~ s/::/\//g;
110             $perl_modules{$key} = $survey->{$_};
111             }
112             @perl_modules = sort keys %perl_modules;
113             @perl_programs = sort keys %perl_programs;
114             }
115             %already_seen = ();
116              
117             # *.pod takes precedence over *.pm
118             sub pod_for {
119             for ($_[0]) {
120             return $_ if /\.pod$/;
121             my $pod = $_;
122             $pod =~ s/\.pm$/\.pod/;
123             if (-e $pod) {
124             return $pod;
125             }
126             return $_;
127             }
128             }
129              
130             # *.pm takes precedence over *.pod
131             sub code_for {
132             for ($_[0]) {
133             return $_ if /\.pm$/;
134             my $pm = $_;
135             $pm =~ s/\.pod$/\.pm/;
136             if (-e $pm) {
137             return $pm;
138             }
139             return $_;
140             }
141             }
142              
143             # cat out a file
144             sub cat {
145             my $file = shift;
146             open(CAT, $file) || return;
147             return join('', );
148             }
149              
150             our @C = (
151              
152             C(
153             Home => [ '/' ],
154             get => sub {
155             my ($self) = @_;
156             $self->v->{title} = $Pod::Server::CONFIG{title};
157             if (defined $self->input->{base}) {
158             $self->v->{base} = 'pod';
159             }
160             $self->render('home');
161             }
162             ),
163              
164             C(
165             Frames => [ '/@frames' ],
166             get => sub {
167             my ($self) = @_;
168             $self->v->{title} = $Pod::Server::CONFIG{title};
169             $self->render('_frames');
170             }
171             ),
172              
173             C(
174             Rescan => [ '/@rescan' ],
175             get => sub {
176             my ($self) = @_;
177             $Pod::Server::Views::HOME = undef;
178             %already_seen = ();
179             %perl_basepods = ();
180             %perl_programs = ();
181             @perl_programs = ();
182             %perl_modules = ();
183             @perl_modules = ();
184             scan();
185             "OK";
186             }
187             ),
188              
189             C(
190             Source => [ '/@source/(.*)' ],
191             get => sub {
192             my ($self, $module) = @_;
193             my $v = $self->v;
194             my $pm = $module; $pm =~ s{/}{::}g;
195             my $pm_file;
196             $v->{path} = [ split('/', $module) ];
197             $v->{title} = "$Pod::Server::CONFIG{title} - $pm";
198             if (exists $perl_modules{$module}) {
199             $v->{file} = code_for $perl_modules{$module};
200             if ($Pod::Server::CONFIG{vim}) {
201             my $vim = Text::VimColor->new(file => $v->{file});
202             $v->{code} = $vim->html;
203             } else {
204             $v->{code} = cat $v->{file};
205             }
206             $self->render('source');
207             } elsif (exists $perl_basepods{$module}) {
208             $v->{file} = code_for $perl_basepods{$module};
209             if ($Pod::Server::CONFIG{vim}) {
210             my $vim = Text::VimColor->new(file => $v->{file});
211             $v->{code} = $vim->html
212             } else {
213             $v->{code} = cat $v->{file};
214             }
215             $self->render('source');
216             } elsif (exists $perl_programs{$module}) {
217             $v->{file} = $perl_programs{$module};
218             if ($Pod::Server::CONFIG{vim}) {
219             my $vim = Text::VimColor->new(file => $v->{file});
220             $v->{code} = $vim->html
221             } else {
222             $v->{code} = cat $v->{file};
223             }
224             $self->render('source');
225             } else {
226             $self->render('pod_not_found');
227             }
228             }
229             ),
230              
231             # The job of this controller is to take $module
232             # and find the file that contains the POD for it.
233             # Then it asks the view to turn the POD into HTML.
234             C(
235             Pod => [ '/(.*)' ],
236             get => sub {
237             my ($self, $module) = @_;
238             my $v = $self->v;
239             my $pm = $module; $pm =~ s{/}{::}g;
240             $v->{path} = [ split('/', $module) ];
241             $v->{module} = $module;
242             $v->{pm} = $pm;
243             if (exists $perl_modules{$module}) {
244             $v->{pod_file} = pod_for $perl_modules{$module};
245             $v->{title} = "$Pod::Server::CONFIG{title} - $pm";
246             $self->render('pod');
247             } elsif (exists $perl_basepods{$module}) {
248             $v->{pod_file} = pod_for $perl_basepods{$module};
249             $v->{title} = "$Pod::Server::CONFIG{title} - $pm";
250             $self->render('pod');
251             } elsif (exists $perl_programs{$module}) {
252             $v->{pod_file} = $perl_programs{$module};
253             $v->{title} = "$Pod::Server::CONFIG{title} - $pm";
254             $self->render('pod');
255             } else {
256             $v->{title} = "$Pod::Server::CONFIG{title} - $pm";
257             $self->render('pod_not_found');
258             }
259             }
260             ),
261              
262             );
263              
264             package Pod::Server::Views;
265             use strict;
266             use warnings;
267             use Data::Dump 'pp';
268             use HTML::AsSubs;
269             use Pod::Simple;
270             use Pod::Simple::HTML;
271             $Pod::Simple::HTML::Perldoc_URL_Prefix = '/';
272              
273             # the ~literal pseudo-element -- don't entity escape this content
274             sub x {
275             HTML::Element->new('~literal', text => $_[0])
276             }
277              
278             our $JS;
279             our $HOME;
280             our $C = \%Pod::Server::CONFIG;
281              
282             our @V = (
283             V(
284             'html',
285              
286             layout => sub {
287             my ($self, $v, @content) = @_;
288             html(
289             head(
290             title($v->{title}),
291             style(x($self->_css)),
292             (
293             $v->{base}
294             ? base({ target => $v->{base} })
295             : ()
296             ),
297             ),
298             body(
299             div({ id => 'menu' },
300             a({ href => R('Home')}, "Home"), ($self->_breadcrumbs($v))
301             ),
302             div({ id => 'pod' }, @content),
303             ),
304             )->as_HTML;
305             },
306              
307             _breadcrumbs => sub {
308             my ($self, $v) = @_;
309             my @breadcrumb;
310             my @path;
311             for (@{$v->{path}}) {
312             push @path, $_;
313             push @breadcrumb, a({ href => R('Pod', join('/', @path)) }, " > $_ ");
314             }
315             @breadcrumb;
316             },
317              
318             _css => sub {qq|
319             body {
320             background: $C->{background_color};
321             color: $C->{foreground_color};
322             font-family: 'Trebuchet MS', sans-serif;
323             font-size: $C->{font_size};
324             }
325             h1, h2, h3, h4 {
326             margin-left: -1em;
327             margin-bottom: 4px;
328             }
329             dl {
330             margin: 0;
331             padding: 0;
332             }
333             dt {
334             margin: 1em 0 1em 1em;
335             }
336             dd {
337             margin: -0.75em 0 0 2em;
338             padding: 0;
339             }
340             em {
341             padding: 0.25em;
342             font-weight: bold;
343             }
344             pre {
345             font-size: 9pt;
346             font-family: "DejaVu Sans Mono", "Bitstream Vera Sans Mono", monospace;
347             background: $C->{pre_background_color};
348             color: $C->{pre_foreground_color};
349             }
350             code {
351             font-size: 9pt;
352             font-weight: bold;
353             color: $C->{code_foreground_color};
354             }
355             a {
356             color: $C->{a_foreground_color};
357             text-decoration: none;
358             }
359             a:hover {
360             color: $C->{a_hover_foreground_color};
361             }
362             div#menu {
363             position: fixed;
364             top: 0;
365             left: 0;
366             width: 100%;
367             background: #000;
368             color: #fff;
369             opacity: 0.75;
370             }
371             ul#list {
372             margin-left: -6em;
373             list-style: none;
374             }
375             div#pod {
376             width: 580px;
377             margin: 2em 4em 2em 4em;
378             }
379             div#pod pre {
380             padding: 0.5em;
381             border: 1px solid #444;
382             border-radius: 7px;
383             }
384             div#pod h1 {
385             font-size: 24pt;
386             border-bottom: 2px solid $C->{a_hover_foreground_color};
387             }
388             div#pod p {
389             margin: 0.75em 0 1em 0;
390             line-height: 1.4em;
391             }
392             |},
393              
394             home => sub {
395             my ($self, $v) = @_;
396             $HOME ||= div(
397             a({ href => R('Home'), target => '_top' }, "no frames"),
398             em(" | "),
399             a({ href => R('Frames'), target => '_top' }, "frames"),
400             ul({ id => 'list' },
401             li(em(">> Modules <<")),
402             (
403             map {
404             my $pm = $_;
405             $pm =~ s{/}{::}g;
406             li(
407             a({ href => R('Pod', $_) }, $pm )
408             )
409             } (sort @perl_modules)
410             ),
411             li(em(">> Executables <<")),
412             (
413             map {
414             li(
415             a({ href => R('Pod', $_) }, $_ )
416             )
417             } (sort @perl_programs),
418             )
419             )
420             );
421             },
422              
423             _frames => sub {
424             my ($self, $v) = @_;
425             html(
426             head(
427             title($v->{title})
428             ),
429             ($C->{sidebar} eq "right"
430             ?
431             frameset({ cols => '*,340' },
432             frame({ name => 'pod', src => R('Pod', $C->{first}) }),
433             frame({ name => 'list', src => R('Home', { base => 'pod' }) }),
434             )
435             :
436             frameset({ cols => '340,*' },
437             frame({ name => 'list', src => R('Home', { base => 'pod' }) }),
438             frame({ name => 'pod', src => R('Pod', $C->{first}) }),
439             )
440             ),
441             )->as_HTML;
442             },
443              
444             pod => sub {
445             my ($self, $v) = @_;
446             my $out;
447             my $pod = Pod::Simple::HTML->new;
448             $pod->index(1);
449             $pod->output_string(\$out);
450             $pod->parse_file($v->{pod_file});
451             $out =~ s/^.*//s;
452             $out =~ s/.*$//s;
453             $out =~ s/^(.*%3A%3A.*)$/my $x = $1; ($x =~ m{indexItem}) ? 1 : $x =~ s{%3A%3A}{\/}g; $x/gme;
454             (
455             x($out),
456             $self->_possibilities($v),
457             $self->_source($v),
458             );
459             },
460              
461             pod_not_found => sub {
462             my ($self, $v) = @_;
463             div(
464             p("POD for $v->{pm} not found."),
465             $self->_possibilities($v)
466             )
467             },
468              
469             _possibilities => sub {
470             my ($self, $v) = @_;
471             my @possibilities = grep { /^$v->{module}/ } @perl_modules;
472             @possibilities = grep { /^$v->{module}/ } @perl_programs if(not(@possibilities));
473             my $colon = sub { my $x = shift; $x =~ s{/}{::}g; $x };
474             hr,
475             ul(
476             map {
477             li(
478             a({ href => R('Pod', $_) }, $colon->($_))
479             )
480             } @possibilities
481             );
482             },
483              
484             _source => sub {
485             my ($self, $v) = @_;
486             hr,
487             h4(a({ href => R('Source', $v->{module} )},
488             "Source Code for " .
489             Pod::Server::Controllers::code_for($v->{pod_file})
490             ));
491             },
492              
493             _vim_syntax_css => sub {qq|
494             .synComment { color: $C->{vim_comment} }
495             .synConstant { color: $C->{vim_constant} }
496             .synIdentifier { color: $C->{vim_identifier} }
497             .synStatement { color: $C->{vim_statement} ; font-weight: bold; }
498             .synPreProc { color: $C->{vim_preproc} }
499             .synType { color: $C->{vim_type} ; font-weight: bold; }
500             .synSpecial { color: $C->{vim_special} }
501             .synUnderlined { color: $C->{vim_underlined} ; text-decoration: underline; }
502             .synError { color: $C->{vim_error_fg} ; background: $C->{vim_error_bg}; }
503             .synTodo { color: $C->{vim_todo_fg} ; background: $C->{vim_todo_bg}; }
504             |},
505              
506             source => sub {
507             my ($self, $v) = @_;
508             style("div#pod { width: auto; }"),
509             ($C->{vim}
510             ?
511             ( style(x($self->_vim_syntax_css)),
512             pre(x($v->{code})) )
513             :
514             ( pre($v->{code}) )
515             )
516             },
517              
518             )
519             );
520              
521             1;
522              
523             __END__