/[gentoo-perl]/g-cpan/trunk/lib/Gentoo/CPAN.pm
Gentoo

Contents of /g-cpan/trunk/lib/Gentoo/CPAN.pm

Parent Directory Parent Directory | Revision Log Revision Log


Revision 234 - (show annotations) (download) (as text)
Mon Feb 15 03:10:12 2010 UTC (5 years, 6 months ago) by robbat2
File MIME type: text/x-perl
File size: 21263 byte(s)
Bug #269173: We run the configure/setup phases prior to build, but we need to remember to block stdin in case the build script tries to read it.
1 package Gentoo::CPAN;
2
3 use 5.008007;
4 use strict;
5 use warnings;
6 use File::Spec;
7 use CPAN;
8 use File::Path;
9 use YAML;
10 use YAML::Node;
11 use Memoize;
12 use Cwd qw(getcwd abs_path cwd);
13 use File::Basename;
14 use Shell qw(perl);
15
16 memoize('transformCPAN');
17 memoize('FindDeps');
18
19 # These libraries were influenced and largely written by
20 # Christian Hartmann <ian@gentoo.org> originally. All of the good
21 # parts are ian's - the rest is mcummings messing around.
22
23 require Exporter;
24
25 our @ISA = qw(Exporter Gentoo );
26
27 our @EXPORT = qw( getCPANInfo makeCPANstub unpackModule transformCPAN
28 );
29
30 our $VERSION = '0.01';
31
32 ##### CPAN CONFIG #####
33 use constant CPAN_CFG_DIR => '.cpan/CPAN';
34 use constant CPAN_CFG_NAME => 'MyConfig.pm';
35
36 # defaults tools for CPAN Config
37 use constant DEF_FTP_PROG => '/usr/bin/ftp';
38 use constant DEF_GPG_PROG => '/usr/bin/gpg';
39 use constant DEF_GZIP_PROG => '/bin/gzip';
40 use constant DEF_LYNX_PROG => '/usr/bin/lynx';
41 use constant DEF_MAKE_PROG => '/usr/bin/make';
42 use constant DEF_NCFTPGET_PROG => '/usr/bin/ncftpget';
43 use constant DEF_LESS_PROG => '/usr/bin/less';
44 use constant DEF_TAR_PROG => '/bin/tar';
45 use constant DEF_UNZIP_PROG => '/usr/bin/unzip';
46 use constant DEF_WGET_PROG => '/usr/bin/wget';
47 use constant DEF_BASH_PROG => '/bin/bash';
48
49 unless ( $ENV{TMPDIR} ) { $ENV{TMPDIR} = '/var/tmp/g-cpan' }
50
51 sub new {
52 my $proto = shift;
53 my %args = @_;
54 my $class = ref($proto) || $proto;
55 my $self = {};
56
57 $self->{cpan} = {};
58 $self->{DEBUG} = $args{debug}||"";
59
60 bless( $self, $class );
61 return $self;
62 }
63
64 ##### - CPAN OVERRIDE - #####
65 #
66 #############################
67
68 *CPAN::myprint = sub {
69 my ($self, $text) = @_;
70 #spinner_start();
71 my @fake_results;
72 # if there is only one result, the string is different
73 chomp($text);
74 if ( $text =~ m{Module } )
75 {
76 $text =~ s{Module id = }{\n};
77 if ($text =~ m{\n}) {
78 $text =~ s{\d+ items found}{};
79 @fake_results = split (/\n/, $text);
80 return(@fake_results);
81
82 }
83 $text =~ s{\n\n}{}gmx;
84 push @fake_results, $text;
85 return (@fake_results) ;
86 }
87 };
88
89 *CPAN::mywarn = sub {
90 return;
91 };
92
93 *CPAN::mydie = sub {
94 my ($self,$what) = @_;
95 print STDOUT "$what";
96 die "\n";
97 };
98
99
100 ########################
101 #
102 ########################
103
104
105 sub getCPANInfo {
106 my $self = shift;
107 my $find_module = shift;
108 my @tmp_v = ();
109
110 unless ($find_module) {
111 croak("No module supplied");
112 }
113
114 if ( $self->{cpan_reload} ) {
115
116 # - User forced reload of the CPAN index >
117 CPAN::Index->force_reload();
118
119 # Reset so we don't run it for every module after the initial reload
120 $self->{cpan_reload} = 0;
121 }
122
123 my $mod;
124
125 unless (($mod = CPAN::Shell->expand("Module",$find_module)) ||
126 ($mod = CPAN::Shell->expand("Bundle",$find_module)) ||
127 ($mod = CPAN::Shell->expand("Distribution",$find_module)) ||
128 ($mod = CPAN::Shell->expandany($find_module)) )
129 { return }
130
131 # - Fetch CPAN-filename and cut out the filename of the tarball.
132 # We are not using $mod->id here because doing so would end up
133 # missing a lot of our ebuilds/packages >
134 # Addendum. Appears we are missing items both ways - have to test both the name in cpan_file and the mod->id. :/
135 next unless ( $mod->id );
136 # manpage_headline spews a bunch of warnings, and is not always valid.
137 # If this is a # CPAN::Bundle, manual work is needed anyway.
138 sub manpage_title {
139 my $mod = shift;
140 # Force the calculation of contents.
141 $mod->as_string();
142 my $module_name = shift;
143 my $desc = $mod->{MANPAGE};
144 return $desc unless $desc;
145 return $desc unless $module_name;
146 $desc =~ s/^$module_name - //g;
147 return $desc;
148 }
149 $self->{'cpan'}{ lc($find_module) }{'description'} =
150 $mod->{RO}{'description'} || manpage_title($mod, $find_module) || "No description available";
151 $self->{'cpan'}{ lc($find_module) }{'src_uri'} = $mod->{RO}{'CPAN_FILE'};
152 $self->{'cpan'}{ lc($find_module) }{'name'} = $mod->id;
153 $self->{'cpan'}{ lc($find_module) }{'version'} = $mod->{RO}{'CPAN_VERSION'}
154 || "0";
155 return;
156 }
157
158 sub unpackModule {
159 my $self = shift;
160 my $module_name = shift;
161 unless (defined($module_name)) { return }
162 if ( $module_name !~ m|::| ) {
163 $module_name =~ s{-}{::}xmsg;
164 } # Assume they gave us module-name instead of module::name
165
166 my $obj = CPAN::Shell->expandany($module_name);
167 unless ( ( ref $obj eq "CPAN::Module" )
168 || ( ref $obj eq "CPAN::Bundle" )
169 || ( ref $obj eq "CPAN::Distribution" ) )
170 {
171 warn("Don't know what '$module_name' is\n");
172 return;
173 }
174 my $file = $obj->cpan_file;
175
176 $CPAN::Config->{prerequisites_policy} = "";
177 $CPAN::Config->{inactivity_timeout} = 10;
178
179 my $pack = $CPAN::META->instance( 'CPAN::Distribution', $file );
180 if ( $pack->can('called_for') ) {
181 $pack->called_for( $obj->id );
182 }
183
184 # Grab the tarball and unpack it
185 unless (defined($pack->{build_dir})) {
186 $pack->get or die "Insufficient permissions!";
187 }
188 my $tmp_dir = $pack->{build_dir};
189
190 # Set our starting point
191 my $localf = $pack->{localfile};
192 $self->{'cpan'}{ lc($module_name) }{'cpan_tarball'} = $pack->{localfile};
193 my ($startdir) = &cwd;
194
195 # chdir to where we were unpacked
196 chdir($tmp_dir) or die "Unable to enter dir $tmp_dir:$!\n";
197
198 # If we have a Makefile.PL, run it to generate Makefile
199 if ( -f "Makefile.PL" ) {
200 perl("Makefile.PL",'</dev/null');
201 }
202
203 # If we have a Build.PL, run it to generate the Build script
204 if ( -f "Build.PL" ) {
205 perl("Build.PL",'</dev/null');
206 }
207
208 # Return whence we came
209 chdir($startdir);
210
211 $pack->unforce if $pack->can("unforce") && exists $obj->{'force_update'};
212 delete $obj->{'force_update'};
213
214 # While we're at it, get the ${S} dir for the ebuld ;)
215 $self->{'cpan'}{ lc($module_name) }{'portage_sdir'} = $pack->{build_dir};
216 $self->{'cpan'}{ lc($module_name) }{'portage_sdir'} =~ s{.*/}{}xmsg;
217 # If name is bundle::, then scan the bundle's deps, otherwise findep it
218 if (lc($module_name) =~ m{^bundle\::})
219 {
220 UnBundle( $self, $tmp_dir, $module_name );
221 } else {
222 FindDeps( $self, $tmp_dir, $module_name );
223 }
224
225 # Most modules don't list module-build as a dep - so we force it if there
226 # is a Build.PL file
227 if ( -f "Build.PL" ) {
228 $self->{'cpan'}{ lc($module_name) }{'depends'}{"Module::Build"} = '0';
229 }
230
231 # Final measure - if somehow we got an undef along the way, set to 0
232 foreach my $dep ( keys %{ $self->{'cpan'}{ lc($module_name) }{'depends'} } )
233 {
234 unless (
235 defined( $self->{'cpan'}{ lc($module_name) }{'depends'}{$dep} ) ||
236 ($self->{'cpan'}{ lc($module_name) }{'depends'}{$dep} eq "undef" )
237 )
238 {
239 $self->{'cpan'}{ lc($module_name) }{'depends'}{$dep} = "0";
240 }
241 }
242 return ($self);
243 }
244
245 sub UnBundle {
246 my $self = shift;
247 my ($workdir) = shift;
248 my $module_name = shift;
249 my ($startdir) = &cwd;
250 chdir($workdir) or die "Unable to enter dir $workdir:$!\n";
251 opendir( CURD, "." );
252 my @dirs = readdir(CURD);
253 closedir(CURD);
254 foreach my $object (@dirs) {
255 next if ( $object eq "." );
256 next if ( $object eq ".." );
257 if ( -f $object ) {
258 if ($object =~ m{\.pm$} )
259 {
260 my $fh;
261 my $in_cont = 0;
262 open ($fh, "$object");
263 while (<$fh>) {
264 $in_cont = m/^=(?!head1\s+CONTENTS)/ ? 0 :
265 m/^=head1\s+CONTENTS/ ? 1 : $in_cont;
266 next unless $in_cont;
267 next if /^=/;
268 s/\#.*//;
269 next if /^\s+$/;
270 chomp;
271 my $module;
272 my $ver = 0;
273 my $junk;
274 if (m{ }) {
275 ($module,$ver,$junk) = (split " ", $_);
276 if ($ver !~ m{^\d+}) { $ver = 0}
277 } else {
278 $module = (split " ", $_, 2)[0];
279 }
280
281 next if ($self->{'cpan'}{ lc($module_name)
282 }{'depends'}{$module});
283 next if (lc($module_name) eq lc($module));
284 $self->{'cpan'}{ lc($module_name) }{'depends'}{$module} = $ver;
285 }
286 }
287 }
288 elsif ( -d $object ) {
289 UnBundle( $self, $object, $module_name );
290 next;
291 }
292
293 }
294 chdir($startdir) or die "Unable to change to dir $startdir:$!\n";
295 return ($self);
296 }
297
298
299 sub FindDeps {
300 my $self = shift;
301 my ($workdir) = shift;
302 my $module_name = shift;
303 my ($startdir) = &cwd;
304 chdir($workdir) or die "Unable to enter dir $workdir:$!\n";
305 opendir( CURD, "." );
306 my @dirs = readdir(CURD);
307 closedir(CURD);
308 my %req_list = ();
309
310 foreach my $object (@dirs) {
311 next if ( $object eq "." );
312 next if ( $object eq ".." );
313 if ( -f $object ) {
314 my $abs_path = abs_path($object);
315 if ( $object eq "META\.yml" ) {
316
317 # Do YAML parsing if you can
318 my $b_n = dirname($abs_path);
319 $b_n = basename($b_n);
320 open(YAML,"$abs_path");
321 my @yaml = <YAML>;
322 close(YAML);
323 if ( check_yaml(@yaml) ) {
324 my $arr = YAML::Load(@yaml);
325 foreach my $type qw(requires build_requires recommends) {
326 if ( my $ar_type = $arr->{$type} ) {
327 foreach my $module ( keys %{$ar_type} ) {
328 next if ( $module eq "" );
329 next if ( $module =~ /Cwd/i );
330 #next if ( lc($module) eq "perl" );
331 next unless ($module);
332 next if (lc($module_name) eq lc($module));
333 $self->{'cpan'}{ lc($module_name) }{'depends'}
334 {$module} = $ar_type->{$module};
335 }
336 }
337 }
338 }
339 }
340 if ( $object =~ m/^Makefile$/ ) {
341
342 # Do some makefile parsing
343 # RIPPED from CPAN.pm ;)
344 use FileHandle;
345
346 my $b_dir = dirname($abs_path);
347 my $makefile = File::Spec->catfile( $b_dir, "Makefile" );
348
349 my $fh;
350 my (%p) = ();
351 if ( $fh = FileHandle->new("<$makefile\0") ) {
352 local ($/) = "\n";
353 while (<$fh>) {
354 chomp;
355 last if /MakeMaker post_initialize section/;
356 my ($p) = m{^[\#]
357 \s{0,}PREREQ_PM\s+=>\s+(.+)
358 }x;
359 next unless $p;
360 while ( $p =~ m/(?:\s)([\w\:]+)=>q\[(.*?)\],?/g ) {
361 my $module = $1;
362 next if ( $module eq "" );
363 next if ( $module =~ /Cwd/i );
364 #next if ( lc($module) eq "perl" );
365 next unless ($module);
366 next if (lc($module_name) eq lc($module));
367 my $version = $2;
368 $self->{'cpan'}{ lc($module_name) }{'depends'}
369 {$module} = $version;
370 }
371
372 last;
373 }
374 }
375 }
376 if ( $object eq "Build.PL" ) {
377
378 # Do some Build file parsing
379 use FileHandle;
380 my $b_dir = dirname($abs_path);
381 my $b_n = dirname($abs_path);
382 $b_n = basename($b_n);
383 my $makefile = File::Spec->catfile( $b_dir, "Build.PL" );
384 my (%p) = ();
385 my $fh;
386
387 foreach my $type qw(requires build_requires) {
388 if ( $fh = FileHandle->new("<$makefile\0") ) {
389 local ($/) = "";
390 while (<$fh>) {
391 chomp;
392 my ($p) = m/^\s+$type\s+=>\s+\{(.*?)(?:\#.*)?\}/smx;
393 next unless $p;
394 undef($/);
395
396 #local($/) = "\n";
397 my @list = split( ',', $p );
398 foreach my $pa (@list) {
399 $pa =~ s/\n|\s+|\'//mg;
400 if ($pa =~ /=~/) {
401 my ($module, $version ) = eval $pa;
402 next if ((!defined($module)) or
403 ( $module eq "" ) or
404 ( $module =~ /Cwd/i ) );
405 #next if ( lc($module) eq "perl" );
406 next unless ($module);
407 next if (lc($module_name) eq lc($module));
408 $self->{'cpan'}{ lc($module_name) }
409 {'depends'}{$module} = $version;
410 }
411 elsif ($pa) {
412 my ( $module, $version ) = split( /=>/, $pa );
413 next if ( $module eq "" );
414 next if ( $module =~ /Cwd/i );
415 #next if ( lc($module) eq "perl" );
416 next unless ($module);
417 $self->{'cpan'}{ lc($module_name) }
418 {'depends'}{$module} = $version;
419 }
420 }
421 last;
422
423 }
424 }
425 }
426
427 }
428
429 }
430 elsif ( -d $object ) {
431 FindDeps( $self, $object, $module_name );
432 next;
433 }
434
435 }
436 chdir($startdir) or die "Unable to change to dir $startdir:$!\n";
437 return ($self);
438
439 }
440
441 sub check_yaml {
442 my @yaml = @_;
443 return 1 if YAML::Load(@yaml);
444 return 0;
445 }
446 sub transformCPAN {
447 my $self = shift;
448 my $name = shift;
449 my $req = shift;
450 return unless ( defined($name) );
451 my $re_path = '(?:.*)?';
452 my $re_pkg = '(?:.*)?';
453 my $re_ver = '(?:v?[\d\.]+[a-z]?\d*)?';
454 my $re_suf = '(?:_(?:alpha|beta|pre|rc|p)(?:\d+)?)?';
455 my $re_rev = '(?:\-r?\d+)?';
456 my $re_ext = '(?:(?:tar|tgz|zip|bz2|gz|tar\.gz))?';
457
458 my $filename = $name;
459 my($modpath, $filenamever, $fileext);
460 $fileext = $1 if $filename =~ s/\.($re_ext)$//;
461 $modpath = $1 if $filename =~ s/^($re_path)\///;
462 $filenamever = $1 if $filename =~ s/-($re_ver$re_suf$re_rev)$//;
463
464 # Alphanumeric version numbers? (http://search.cpan.org/~pip/)
465 if ($filename =~ s/-(\d\.\d\.\d)([A-Za-z0-9]{6})$//) {
466 $filenamever = $1;
467 $filenamever .= ('.'.ord($_)) foreach split(//, $2);
468 }
469
470 # remove underscores
471 return unless ($filename);
472 $filename =~ tr/A-Za-z0-9\./-/c;
473 $filename =~ s/\.pm//; # e.g. CGI.pm
474
475 # Remove double .'s - happens on occasion with odd packages
476 $filenamever =~ s/\.$//;
477
478 # rename a double version -0.55-7 to ebuild style -0.55-r7
479 $filenamever =~ s/([0-9.]+)-([0-9.]+)$/$1\.$2/;
480
481 # Remove leading v's - happens on occasion
482 $filenamever =~ s{^v}{}i;
483
484 # Some modules don't use the /\d\.\d\d/ convention, and portage goes
485 # berserk if the ebuild is called ebulldname-.02.ebuild -- so we treat
486 # this special case
487 if ( substr( $filenamever, 0, 1 ) eq '.' ) {
488 $filenamever = 0 . $filenamever;
489 }
490 if ($req eq "v")
491 {
492 return ($filenamever);
493 }
494 else
495 {
496 return ($filename);
497 }
498 }
499
500 sub makeCPANstub {
501 my $self = shift;
502 my $cpan_cfg_dir = File::Spec->catfile( $ENV{HOME}, CPAN_CFG_DIR );
503 my $cpan_cfg_file = File::Spec->catfile( $cpan_cfg_dir, CPAN_CFG_NAME );
504
505 if ( not -d $cpan_cfg_dir ) {
506 mkpath( $cpan_cfg_dir, 1, 0755 )
507 or fatal( $Gentoo::ERR_FOLDER_CREATE, $cpan_cfg_dir, $! );
508 }
509
510 my $tmp_dir = -d $ENV{TMPDIR} ? $ENV{TMPDIR} : $ENV{HOME};
511 my $ftp_proxy = defined( $ENV{ftp_proxy} ) ? $ENV{ftp_proxy} : '';
512 my $http_proxy = defined( $ENV{http_proxy} ) ? $ENV{http_proxy} : '';
513 my $user_shell = defined( $ENV{SHELL} ) ? $ENV{SHELL} : DEF_BASH_PROG;
514 my $ftp_prog = -f DEF_FTP_PROG ? DEF_FTP_PROG : '';
515 my $gpg_prog = -f DEF_GPG_PROG ? DEF_GPG_PROG : '';
516 my $gzip_prog = -f DEF_GZIP_PROG ? DEF_GZIP_PROG : '';
517 my $lynx_prog = -f DEF_LYNX_PROG ? DEF_LYNX_PROG : '';
518 my $make_prog = -f DEF_MAKE_PROG ? DEF_MAKE_PROG : '';
519 my $ncftpget_prog = -f DEF_NCFTPGET_PROG ? DEF_NCFTPGET_PROG : '';
520 my $less_prog = -f DEF_LESS_PROG ? DEF_LESS_PROG : '';
521 my $tar_prog = -f DEF_TAR_PROG ? DEF_TAR_PROG : '';
522 my $unzip_prog = -f DEF_UNZIP_PROG ? DEF_UNZIP_PROG : '';
523 my $wget_prog = -f DEF_WGET_PROG ? DEF_WGET_PROG : '';
524
525 open CPANCONF, ">$cpan_cfg_file"
526 or fatal( $Gentoo::ERR_FOLDER_CREATE, $cpan_cfg_file, $! );
527 print CPANCONF <<"SHERE";
528
529 # This is CPAN.pm's systemwide configuration file. This file provides
530 # defaults for users, and the values can be changed in a per-user
531 # configuration file. The user-config file is being looked for as
532 # ~/.cpan/CPAN/MyConfig\.pm. This was generated by g-cpan for temporary usage
533
534 \$CPAN::Config = {
535 'build_cache' => q[10],
536 'build_dir' => q[$tmp_dir/.cpan/build],
537 'cache_metadata' => q[1],
538 'cpan_home' => q[$tmp_dir/.cpan],
539 'dontload_hash' => { },
540 'ftp' => q[$ftp_prog],
541 'ftp_proxy' => q[$ftp_proxy],
542 'getcwd' => q[cwd],
543 'gpg' => q[$gpg_prog],
544 'gzip' => q[$gzip_prog],
545 'histfile' => q[$tmp_dir/.cpan/histfile],
546 'histsize' => q[100],
547 'http_proxy' => q[$http_proxy],
548 'inactivity_timeout' => q[0],
549 'index_expire' => q[1],
550 'inhibit_startup_message' => q[1],
551 'keep_source_where' => q[$tmp_dir/.cpan/sources],
552 'lynx' => q[$lynx_prog],
553 'make' => q[$make_prog],
554 'make_arg' => q[],
555 'make_install_arg' => q[],
556 'make_install_make_command' => q[/usr/bin/make],
557 'makepl_arg' => q[],
558 'mbuild_arg' => q[],
559 'mbuild_install_arg' => q[],
560 'mbuild_install_build_command' => q[./Build],
561 'mbuildpl_arg' => q[],
562 'ncftpget' => q[$ncftpget_prog],
563 'no_proxy' => q[],
564 'pager' => q[$less_prog],
565 'prerequisites_policy' => q[follow],
566 'scan_cache' => q[atstart],
567 'shell' => q[$user_shell],
568 'tar' => q[$tar_prog],
569 'term_is_latin' => q[1],
570 'unzip' => q[$unzip_prog],
571 'urllist' => [qw[http://search.cpan.org/CPAN http://www.cpan.org/pub/CPAN ],],
572 'wget' => q[$wget_prog],
573 };
574 1;
575 __END__
576
577 SHERE
578
579 close CPANCONF;
580 }
581
582 1;
583
584 =pod
585
586 =head1 NAME
587
588 Gentoo::CPAN - Perform CPAN calls, emulating some functionality where possible
589 for a portage friendly environment
590
591 =head1 SYNOPSIS
592
593 use Gentoo::CPAN;
594 my $obj = Gentoo::CPAN->new();
595 $obj->getCPANInfo("Module::Build");
596 my $version = $obj->{cpan}->{lc("Module::Build")}->{'version'};
597 my $realname = $obj->{cpan}->{lc($module)}->{'name'};
598 my $srcuri = $obj->{cpan}->{lc($module)}->{'src_uri'};
599 my $desc = $obj->{cpan}->{lc($module)}->{'description'};
600
601 =head1 DESCRIPTION
602
603 The C<Gentoo::CPAN> class gives us a method of working with CPAN modules. In
604 part it emulates the behavior of L<CPAN> itself, in other parts it simply
605 relies on L<CPAN> to do the work for us.
606
607 =head1 METHODS
608
609 =over 4
610
611 =item my $obj = Gentoo::CPAN->new();
612
613 Create a new Gentoo CPAN object.
614
615 =item $obj->getCPANInfo($somemodule);
616
617 Given the name of a CPAN module, extract the information from CPAN on this
618 object and populate the $obj hash. Provides:
619
620 =over 4
621
622 =item $obj->{cpan}{lc($module)}{'version'}
623
624 Version number
625
626 =item $obj->{cpan}{lc($module)}{'name'}
627
628 CPAN's name for the distribution
629
630 =item $obj->{cpan}{lc($module)}{'src_uri'}
631
632 The context path on cpan for the module source
633
634 =item $obj->{cpan}{lc($module)}{'description'}
635
636 Description, if available
637
638 =back
639
640 =item $obj->unpackModule($somemodule)
641
642 Grabs the module from CPAN and unpacks it. It then procedes to scan for
643 dependencies, filling in $obj->{'cpan'}{lc($somemodule)}{'depends'} with and
644 deps that were found (hash).
645
646 =item $obj->transformCPANVersion($somemodule)
647
648 =item $obj->transformCPANName($somemodule)
649
650 Returns a portage friend version or module name from the name that is used on
651 CPAN. Useful for modules that use names or versions that would break as a
652 portage ebuild.
653
654 =item $obj->makeCPANstub()
655
656 Generates a default CPAN stub file if none exists in the user's environment
657
658 =back
659
660 =cut

  ViewVC Help
Powered by ViewVC 1.1.20