File Coverage

bin/perldolicious
Criterion Covered Total %
statement 46 65 70.7
branch 4 16 25.0
condition n/a
subroutine 14 17 82.3
pod n/a
total 64 98 65.3


line stmt bran cond sub pod time code
1             #!/usr/bin/env perl
2             # -*- mode: perl -*- # cperl-mode doesn't recognize the DATA section -_-
3              
4 1     1   1609 use Mojolicious::Lite;
  1         153767  
  1         8  
5 1     1   34388 use 5.010;
  1         5  
  1         34  
6              
7 1     1   18 use File::Path ();
  1         1  
  1         18  
8 1     1   5 use File::Spec::Functions qw(catfile);
  1         1  
  1         47  
9 1     1   1241 use File::Temp ();
  1         10170  
  1         22  
10 1     1   8 use File::Which ();
  1         1  
  1         14  
11 1     1   886 use IO::Zlib ();
  1         139587  
  1         25  
12 1     1   8 use Mojo::UserAgent ();
  1         3  
  1         13  
13 1     1   4030 use Module::CoreList ();
  1         87803  
  1         523  
14 1     1   1047 use Module::Path ();
  1         616  
  1         23  
15 1     1   8 use Mojo::JSON ();
  1         1  
  1         15  
16 1     1   8 use Scalar::Util ();
  1         2  
  1         15  
17 1     1   93597 use Storable ();
  1         5465  
  1         2259  
18              
19             our $VERSION = '0.011';
20             $VERSION = eval $VERSION;
21              
22             sub get_modules {
23 0     0   0 my $cpan_package_file = shift;
24 0 0       0 die "$cpan_package_file doesn't exist" unless -f $cpan_package_file;
25              
26 0 0       0 my $fh = IO::Zlib->new($cpan_package_file, 'r')
27             or die "Could not open '$cpan_package_file': $!";
28              
29 0         0 my $modules;
30 0         0 while (<$fh>) {
31 0         0 my @columns = split /\s+/;
32 0 0       0 next unless @columns == 3;
33 0         0 my $module = $columns[0];
34 0         0 push @$modules, $module;
35             }
36              
37 0         0 return $modules;
38             }
39              
40             sub write_modules {
41 0     0   0 my ($modules_cache_file, $cpan_package_file) = @_;
42              
43 0         0 my $modules = get_modules($cpan_package_file);
44 0         0 Storable::nstore($modules, $modules_cache_file);
45             }
46              
47             sub compile_pattern {
48 3     3   6 my ($pattern, $p) = @_;
49 3 50       17 die "No pattern specified\n" unless $pattern;
50              
51 3         6 local $@;
52 3 50       8 eval { $pattern = $p->{ignore_case} ? qr{$pattern}i : qr{$pattern}; };
  3         124  
53 3 100       2679 die "Invalid regular expression\n" if $@;
54              
55 2         8 return $pattern;
56             }
57              
58             sub read_json_config {
59 0     0     my $config_file = shift;
60              
61 0 0         open my $fh, '<', $config_file
62             or die "Could not open '$config_file': $!";
63              
64 0           local $/;
65 0           my $bytes = <$fh>;
66              
67 0 0         close $fh or die "Could not close '$config_file': $!";
68 0           return Mojo::JSON->new()->decode($bytes);
69             }
70              
71             helper setup_modules_file => sub {
72             my $cache_file = app->config('modules_cache_file');
73             my $limit = app->config('auto_download');
74              
75             die "Configuration auto_download: $limit doesn't look like number\n"
76             unless Scalar::Util::looks_like_number($limit);
77              
78             # file does not exist or file exists but it's older than the
79             # specified limit (in days)
80             if (not -f $cache_file
81             or -f $cache_file and -M $cache_file > $limit)
82             {
83              
84             if (-f $cache_file) {
85             app->log->info("Deleting cache file $cache_file");
86             unlink $cache_file or die $!;
87             }
88              
89             my $url = 'http://www.cpan.org/modules/02packages.details.txt.gz';
90              
91             my $tempfile = File::Temp->new;
92             my $download_location = $tempfile->filename;
93              
94             app->log->info("Downloading $url to $download_location");
95             Mojo::UserAgent->new->get($url)
96             ->res->content->asset->move_to($download_location);
97              
98             app->log->info("Caching modules to $cache_file");
99             write_modules($cache_file, $download_location);
100              
101             }
102             };
103              
104             helper setup_log => sub {
105             my ($self, $confdir) = @_;
106             my $logfile = catfile($confdir, 'server.log');
107             my $limit = 2_000_000; # 2MB
108             if (-f $logfile and -s $logfile > $limit) {
109             unlink $logfile or die "Could not delete '$logfile'";
110             app->log->info("Deleting $logfile");
111             }
112              
113             app->log->path($logfile) if app->config('quiet');
114             };
115              
116             helper initialize => sub {
117             # for some reason cpandoc is not installed on some machines, even though
118             # Pod::Cpandoc is already listed as a prereq in Build.PL
119             die "cpandoc is not installed on your system\n"
120             unless File::Which::which('cpandoc');
121              
122             my $confdir = $ENV{PERLDOLICIOUS_HOME}
123             || catfile($ENV{HOME}, '.perldolicious');
124              
125             unless (-d $confdir) {
126             File::Path::make_path($confdir)
127             or die "Could not create directory $confdir";
128             }
129              
130             my $confile = catfile($confdir, 'config.json');
131             my $conf = {
132             modules_cache_file => catfile($confdir, 'modules.storable'),
133             auto_download => 14, # 2 weeks
134             quiet => 0
135             };
136              
137             if (-f $confile) {
138             my $user_conf = read_json_config($confile) || {};
139             %$conf = (%$conf, %$user_conf); # merge config
140             }
141              
142             app->config($_ => $conf->{$_}) for keys %$conf;
143              
144             app->setup_log($confdir);
145             app->setup_modules_file;
146              
147             app->log->info('*** STARTING A NEW SESSION ***');
148             app->log->info("Config dir: $confdir");
149             app->log->info("Loaded config file: $confile") if -f $confile;
150             app->log->info('Loaded cache file: ' . app->config('modules_cache_file'));
151             };
152              
153             helper find_modules => sub {
154             my ($self, $pattern, $p) = @_;
155              
156             $pattern = compile_pattern($pattern, $p);
157             my $modules_cache_file = app->config('modules_cache_file');
158              
159             state $modules = Storable::retrieve($modules_cache_file);
160             return [grep { /$pattern/ } @$modules];
161             };
162              
163             helper perldoc => sub {
164             my ($self, $module, $p) = @_;
165             my $args = $p->{source} ? '-m' : '-t';
166             chomp(my $doc = `cpandoc $args $module`);
167             return $doc;
168             };
169              
170             get '/' => sub {
171             my $self = shift;
172              
173             $self->render(
174             template => 'index',
175             action_url => $self->url_for('/results'),
176             );
177             };
178              
179             post '/results' => sub {
180             my $self = shift;
181             my ($pattern, $ignore_case) =
182             ($self->param('pattern'), $self->param('ignoreCase'));
183              
184             my $modules;
185              
186             local $@;
187              
188             eval {
189             $modules =
190             $self->find_modules($pattern, {ignore_case => $ignore_case});
191             };
192              
193             if ($@) {
194             $self->render_exception($@);
195             }
196             elsif (@$modules) {
197             $self->render(
198             pattern => $pattern,
199             modules => $modules,
200             matches => scalar(@$modules),
201             );
202             }
203             else {
204             $self->render_exception("Could not find modules that match $pattern");
205             }
206             };
207              
208             get '/doc/:module' => sub {
209             my $self = shift;
210             my $module = $self->param('module');
211              
212             (my $distname = $module) =~ s{::}{-}g;
213             my $release_date = Module::CoreList->first_release($module);
214              
215             my @known_temp_dirs = (qr{/var/folders}, qr{/tmp/}, qr{Local\\Temp});
216             my $location = Module::Path::module_path($module);
217              
218             $location = undef if $location and grep { /$location/ } @known_temp_dirs;
219              
220             $self->render(
221             template => "doc",
222             module => $module,
223             distname => $distname,
224             doc => $self->perldoc($module),
225             location => $location,
226             release_date => $release_date,
227             source_code_url => $self->url_for("/doc/$module/source"),
228             );
229             };
230              
231             get '/doc/:module/source' => sub {
232             my $self = shift;
233             my $module = $self->param('module');
234             $self->render(
235             module => $module,
236             template => 'source',
237             source_code => $self->perldoc($module, {source => 1}),
238             );
239             };
240              
241             app->mode('production');
242             app->initialize;
243             app->defaults(layout => 'index');
244             app->start;
245              
246             __DATA__