Commit | Line | Data |
---|---|---|
adfe19db MHM |
1 | ################################################################################ |
2 | ## | |
3 | ## $Revision: 19 $ | |
4 | ## $Author: mhx $ | |
5 | ## $Date: 2004/08/16 10:58:27 +0200 $ | |
6 | ## | |
7 | ################################################################################ | |
8 | ## | |
9 | ## Version 3.x, Copyright (C) 2004, Marcus Holland-Moritz. | |
10 | ## Version 2.x, Copyright (C) 2001, Paul Marquess. | |
11 | ## Version 1.x, Copyright (C) 1999, Kenneth Albanowski. | |
12 | ## | |
13 | ## This program is free software; you can redistribute it and/or | |
14 | ## modify it under the same terms as Perl itself. | |
15 | ## | |
16 | ################################################################################ | |
17 | ||
18 | =provides | |
19 | ||
20 | =implementation | |
21 | ||
22 | =cut | |
23 | ||
24 | use strict; | |
25 | ||
26 | my %opt = ( | |
27 | quiet => 0, | |
28 | diag => 1, | |
29 | hints => 1, | |
30 | changes => 1, | |
31 | cplusplus => 0, | |
32 | ); | |
33 | ||
34 | my($ppport) = $0 =~ /([\w.]+)$/; | |
35 | my $LF = '(?:\r\n|[\r\n])'; # line feed | |
36 | my $HS = "[ \t]"; # horizontal whitespace | |
37 | ||
38 | eval { | |
39 | require Getopt::Long; | |
40 | Getopt::Long::GetOptions(\%opt, qw( | |
41 | help quiet diag! hints! changes! cplusplus | |
42 | patch=s copy=s diff=s compat-version=s | |
43 | list-provided list-unsupported | |
44 | )) or usage(); | |
45 | }; | |
46 | ||
47 | if ($@ and grep /^-/, @ARGV) { | |
48 | usage() if "@ARGV" =~ /^--?h(?:elp)?$/; | |
49 | die "Getopt::Long not found. Please don't use any options.\n"; | |
50 | } | |
51 | ||
52 | usage() if $opt{help}; | |
53 | ||
54 | if (exists $opt{'compat-version'}) { | |
55 | my($r,$v,$s) = eval { parse_version($opt{'compat-version'}) }; | |
56 | if ($@) { | |
57 | die "Invalid version number format: '$opt{'compat-version'}'\n"; | |
58 | } | |
59 | die "Only Perl 5 is supported\n" if $r != 5; | |
60 | die "Invalid version number: $opt{'compat-version'}\n" if $v >= 1000 || $v >= 1000; | |
61 | $opt{'compat-version'} = sprintf "%d.%03d%03d", $r, $v, $s; | |
62 | } | |
63 | else { | |
64 | $opt{'compat-version'} = 5; | |
65 | } | |
66 | ||
67 | # Never use C comments in this file!!!!! | |
68 | my $ccs = '/'.'*'; | |
69 | my $cce = '*'.'/'; | |
70 | my $rccs = quotemeta $ccs; | |
71 | my $rcce = quotemeta $cce; | |
72 | ||
73 | my @files; | |
74 | ||
75 | if (@ARGV) { | |
76 | @files = map { glob $_ } @ARGV; | |
77 | } | |
78 | else { | |
79 | eval { | |
80 | require File::Find; | |
81 | File::Find::find(sub { | |
82 | $File::Find::name =~ /\.(xs|c|h|cc)$/i | |
83 | and push @files, $File::Find::name; | |
84 | }, '.'); | |
85 | }; | |
86 | if ($@) { | |
87 | @files = map { glob $_ } qw(*.xs *.c *.h *.cc); | |
88 | } | |
89 | my %filter = map { /(.*)\.xs$/ ? ("$1.c" => 1) : () } @files; | |
90 | @files = grep { !/\b\Q$ppport\E$/i && !exists $filter{$_} } @files; | |
91 | } | |
92 | ||
93 | unless (@files) { | |
94 | die "No input files given!\n"; | |
95 | } | |
96 | ||
97 | my %API = map { /^(\w+)\|([^|]*)\|([^|]*)\|(\w*)$/ | |
98 | ? ( $1 => { | |
99 | ($2 ? ( base => $2 ) : ()), | |
100 | ($3 ? ( todo => $3 ) : ()), | |
101 | (index($4, 'v') >= 0 ? ( varargs => 1 ) : ()), | |
102 | (index($4, 'p') >= 0 ? ( provided => 1 ) : ()), | |
103 | (index($4, 'n') >= 0 ? ( nothxarg => 1 ) : ()), | |
104 | } ) | |
105 | : die "invalid spec: $_" } qw( | |
106 | __PERL_API__ | |
107 | ); | |
108 | ||
109 | if (exists $opt{'list-unsupported'}) { | |
110 | my $f; | |
111 | for $f (sort { lc $a cmp lc $b } keys %API) { | |
112 | next unless $API{$f}{todo}; | |
113 | print "$f ", '.'x(40-length($f)), " ", format_version($API{$f}{todo}), "\n"; | |
114 | } | |
115 | exit 0; | |
116 | } | |
117 | ||
118 | # Scan for possible replacement candidates | |
119 | ||
120 | my(%replace, %need, %hints, %depends); | |
121 | my $replace = 0; | |
122 | my $hint = ''; | |
123 | ||
124 | while (<DATA>) { | |
125 | if ($hint) { | |
126 | if (m{^\s*\*\s(.*?)\s*$}) { | |
127 | $hints{$hint} ||= ''; # suppress warning with older perls | |
128 | $hints{$hint} .= "$1\n"; | |
129 | } | |
130 | else { | |
131 | $hint = ''; | |
132 | } | |
133 | } | |
134 | $hint = $1 if m{^\s*$rccs\sHint:\s+(\w+)\s*$}; | |
135 | ||
136 | $replace = $1 if m{^\s*$rccs\s+Replace:\s+(\d+)\s+$rcce\s*$}; | |
137 | $replace{$2} = $1 if $replace and m{^\s*#\s*define\s+(\w+)(?:\([^)]*\))?\s+(\w+)}; | |
138 | $replace{$2} = $1 if m{^\s*#\s*define\s+(\w+)(?:\([^)]*\))?\s+(\w+).*$rccs\s+Replace\s+$rcce}; | |
139 | $replace{$1} = $2 if m{^\s*$rccs\s+Replace (\w+) with (\w+)\s+$rcce\s*$}; | |
140 | ||
141 | if (m{^\s*$rccs\s+(\w+)\s+depends\s+on\s+(\w+(\s*,\s*\w+)*)\s+$rcce\s*$}) { | |
142 | push @{$depends{$1}}, map { s/\s+//g; $_ } split /,/, $2; | |
143 | } | |
144 | ||
145 | $need{$1} = 1 if m{^#if\s+defined\(NEED_(\w+)(?:_GLOBAL)?\)}; | |
146 | } | |
147 | ||
148 | if (exists $opt{'list-provided'}) { | |
149 | my $f; | |
150 | for $f (sort { lc $a cmp lc $b } keys %API) { | |
151 | next unless $API{$f}{provided}; | |
152 | my @flags; | |
153 | push @flags, 'explicit' if exists $need{$f}; | |
154 | push @flags, 'depend' if exists $depends{$f}; | |
155 | push @flags, 'hint' if exists $hints{$f}; | |
156 | my $flags = @flags ? ' ['.join(', ', @flags).']' : ''; | |
157 | print "$f$flags\n"; | |
158 | } | |
159 | exit 0; | |
160 | } | |
161 | ||
162 | my(%files, %global, %revreplace); | |
163 | %revreplace = reverse %replace; | |
164 | my $filename; | |
165 | my $patch_opened = 0; | |
166 | ||
167 | for $filename (@files) { | |
168 | unless (open IN, "<$filename") { | |
169 | warn "Unable to read from $filename: $!\n"; | |
170 | next; | |
171 | } | |
172 | ||
173 | info("Scanning $filename ..."); | |
174 | ||
175 | my $c = do { local $/; <IN> }; | |
176 | close IN; | |
177 | ||
178 | my %file = (orig => $c, changes => 0); | |
179 | ||
180 | # temporarily remove C comments from the code | |
181 | my @ccom; | |
182 | $c =~ s{ | |
183 | ( | |
184 | [^"'/]+ | |
185 | | | |
186 | (?:"[^"\\]*(?:\\.[^"\\]*)*" [^"'/]*)+ | |
187 | | | |
188 | (?:'[^'\\]*(?:\\.[^'\\]*)*' [^"'/]*)+ | |
189 | ) | |
190 | | | |
191 | (/ (?: | |
192 | \*[^*]*\*+(?:[^$ccs][^*]*\*+)* / | |
193 | | | |
194 | /[^\r\n]* | |
195 | )) | |
196 | }{ | |
197 | defined $2 and push @ccom, $2; | |
198 | defined $1 ? $1 : "$ccs$#ccom$cce"; | |
199 | }egsx; | |
200 | ||
201 | $file{ccom} = \@ccom; | |
202 | $file{code} = $c; | |
203 | $file{has_inc_ppport} = ($c =~ /#.*include.*\Q$ppport\E/); | |
204 | ||
205 | my $func; | |
206 | ||
207 | for $func (keys %API) { | |
208 | my $match = $func; | |
209 | $match .= "|$revreplace{$func}" if exists $revreplace{$func}; | |
210 | if ($c =~ /\b(?:Perl_)?($match)\b/) { | |
211 | $file{uses_replace}{$1}++ if exists $revreplace{$func} && $1 eq $revreplace{$func}; | |
212 | $file{uses_Perl}{$func}++ if $c =~ /\bPerl_$func\b/; | |
213 | if (exists $API{$func}{provided}) { | |
214 | if (!exists $API{$func}{base} || $API{$func}{base} > $opt{'compat-version'}) { | |
215 | $file{uses}{$func}++; | |
216 | push @{$global{uses}{$func}}, $filename; | |
217 | my @deps = rec_depend($func); | |
218 | if (@deps) { | |
219 | $file{uses_deps}{$func} = \@deps; | |
220 | for (@deps) { | |
221 | $file{uses}{$_} = 0 unless exists $file{uses}{$_}; | |
222 | push @{$global{uses}{$_}}, $filename; | |
223 | } | |
224 | } | |
225 | for ($func, @deps) { | |
226 | if (exists $need{$_}) { | |
227 | $file{needs}{$_} = 'static'; | |
228 | push @{$global{needs}{$_}}, $filename; | |
229 | } | |
230 | } | |
231 | } | |
232 | } | |
233 | if (exists $API{$func}{todo} && $API{$func}{todo} > $opt{'compat-version'}) { | |
234 | if ($c =~ /\b$func\b/) { | |
235 | $file{uses_todo}{$func}++; | |
236 | push @{$global{uses_todo}{$func}}, $filename; | |
237 | } | |
238 | } | |
239 | } | |
240 | } | |
241 | ||
242 | while ($c =~ /^$HS*#$HS*define$HS+(NEED_(\w+?)(_GLOBAL)?)\b/mg) { | |
243 | if (exists $need{$2}) { | |
244 | $file{defined $3 ? 'needed_global' : 'needed_static'}{$2}++; | |
245 | push @{$global{defined $3 ? 'needed_global' : 'needed_static'}{$2}}, $filename; | |
246 | } | |
247 | else { | |
248 | warning("Possibly wrong #define $1 in $filename"); | |
249 | } | |
250 | } | |
251 | ||
252 | $files{$filename} = \%file; | |
253 | } | |
254 | ||
255 | # Globally resolve NEED_'s | |
256 | my $need; | |
257 | for $need (keys %{$global{needs}}) { | |
258 | if (@{$global{needs}{$need}} > 1) { | |
259 | my @targets = @{$global{needs}{$need}}; | |
260 | my @t = grep $files{$_}{needed_global}{$need}, @targets; | |
261 | @targets = @t if @t; | |
262 | @t = grep /\.xs$/i, @targets; | |
263 | @targets = @t if @t; | |
264 | my $target = shift @targets; | |
265 | $files{$target}{needs}{$need} = 'global'; | |
266 | for (@{$global{needs}{$need}}) { | |
267 | $files{$_}{needs}{$need} = 'extern' if $_ ne $target; | |
268 | } | |
269 | } | |
270 | } | |
271 | ||
272 | for $filename (@files) { | |
273 | exists $files{$filename} or next; | |
274 | ||
275 | info("=== Analyzing $filename ==="); | |
276 | ||
277 | my %file = %{$files{$filename}}; | |
278 | my $func; | |
279 | my $c = $file{code}; | |
280 | ||
281 | for $func (sort keys %{$file{uses_Perl}}) { | |
282 | if ($API{$func}{varargs}) { | |
283 | my $changes = ($c =~ s{\b(Perl_$func\s*\(\s*)(?!aTHX_?)(\)|[^\s)]*\))} | |
284 | { $1 . ($2 eq ')' ? 'aTHX' : 'aTHX_ ') . $2 }ge); | |
285 | if ($changes) { | |
286 | warning("Doesn't pass interpreter argument aTHX to Perl_$func"); | |
287 | $file{changes} += $changes; | |
288 | } | |
289 | } | |
290 | else { | |
291 | warning("Uses Perl_$func instead of $func"); | |
292 | $file{changes} += ($c =~ s{\bPerl_$func(\s*)\((\s*aTHX_?)?\s*} | |
293 | {$func$1(}g); | |
294 | } | |
295 | } | |
296 | ||
297 | for $func (sort keys %{$file{uses_replace}}) { | |
298 | warning("Uses $func instead of $replace{$func}"); | |
299 | $file{changes} += ($c =~ s/\b$func\b/$replace{$func}/g); | |
300 | } | |
301 | ||
302 | for $func (sort keys %{$file{uses}}) { | |
303 | next unless $file{uses}{$func}; # if it's only a dependency | |
304 | if (exists $file{uses_deps}{$func}) { | |
305 | diag("Uses $func, which depends on ", join(', ', @{$file{uses_deps}{$func}})); | |
306 | } | |
307 | elsif (exists $replace{$func}) { | |
308 | warning("Uses $func instead of $replace{$func}"); | |
309 | $file{changes} += ($c =~ s/\b$func\b/$replace{$func}/g); | |
310 | } | |
311 | else { | |
312 | diag("Uses $func"); | |
313 | } | |
314 | hint($func); | |
315 | } | |
316 | ||
317 | for $func (sort keys %{$file{uses_todo}}) { | |
318 | warning("Uses $func, which may not be portable below perl ", | |
319 | format_version($API{$func}{todo})); | |
320 | } | |
321 | ||
322 | for $func (sort keys %{$file{needed_static}}) { | |
323 | my $message = ''; | |
324 | if (not exists $file{uses}{$func}) { | |
325 | $message = "No need to define NEED_$func if $func is never used"; | |
326 | } | |
327 | elsif (exists $file{needs}{$func} && $file{needs}{$func} ne 'static') { | |
328 | $message = "No need to define NEED_$func when already needed globally"; | |
329 | } | |
330 | if ($message) { | |
331 | diag($message); | |
332 | $file{changes} += ($c =~ s/^$HS*#$HS*define$HS+NEED_$func\b.*$LF//mg); | |
333 | } | |
334 | } | |
335 | ||
336 | for $func (sort keys %{$file{needed_global}}) { | |
337 | my $message = ''; | |
338 | if (not exists $global{uses}{$func}) { | |
339 | $message = "No need to define NEED_${func}_GLOBAL if $func is never used"; | |
340 | } | |
341 | elsif (exists $file{needs}{$func}) { | |
342 | if ($file{needs}{$func} eq 'extern') { | |
343 | $message = "No need to define NEED_${func}_GLOBAL when already needed globally"; | |
344 | } | |
345 | elsif ($file{needs}{$func} eq 'static') { | |
346 | $message = "No need to define NEED_${func}_GLOBAL when only used in this file"; | |
347 | } | |
348 | } | |
349 | if ($message) { | |
350 | diag($message); | |
351 | $file{changes} += ($c =~ s/^$HS*#$HS*define$HS+NEED_${func}_GLOBAL\b.*$LF//mg); | |
352 | } | |
353 | } | |
354 | ||
355 | $file{needs_inc_ppport} = keys %{$file{uses}}; | |
356 | ||
357 | if ($file{needs_inc_ppport}) { | |
358 | my $pp = ''; | |
359 | ||
360 | for $func (sort keys %{$file{needs}}) { | |
361 | my $type = $file{needs}{$func}; | |
362 | next if $type eq 'extern'; | |
363 | my $suffix = $type eq 'global' ? '_GLOBAL' : ''; | |
364 | unless (exists $file{"needed_$type"}{$func}) { | |
365 | if ($type eq 'global') { | |
366 | diag("Files [@{$global{needs}{$func}}] need $func, adding global request"); | |
367 | } | |
368 | else { | |
369 | diag("File needs $func, adding static request"); | |
370 | } | |
371 | $pp .= "#define NEED_$func$suffix\n"; | |
372 | } | |
373 | } | |
374 | ||
375 | if ($pp && ($c =~ s/^(?=$HS*#$HS*define$HS+NEED_\w+)/$pp/m)) { | |
376 | $pp = ''; | |
377 | $file{changes}++; | |
378 | } | |
379 | ||
380 | unless ($file{has_inc_ppport}) { | |
381 | diag("Needs to include '$ppport'"); | |
382 | $pp .= qq(#include "$ppport"\n) | |
383 | } | |
384 | ||
385 | if ($pp) { | |
386 | $file{changes} += ($c =~ s/^($HS*#$HS*define$HS+NEED_\w+.*?)^/$1$pp/ms) | |
387 | || ($c =~ s/^(?=$HS*#$HS*include.*\Q$ppport\E)/$pp/m) | |
388 | || ($c =~ s/^($HS*#$HS*include.*XSUB.*\s*?)^/$1$pp/m) | |
389 | || ($c =~ s/^/$pp/); | |
390 | } | |
391 | } | |
392 | else { | |
393 | if ($file{has_inc_ppport}) { | |
394 | diag("No need to include '$ppport'"); | |
395 | $file{changes} += ($c =~ s/^$HS*?#$HS*include.*\Q$ppport\E.*?$LF//m); | |
396 | } | |
397 | } | |
398 | ||
399 | # put back in our C comments | |
400 | my $ix; | |
401 | my $cppc = 0; | |
402 | my @ccom = @{$file{ccom}}; | |
403 | for $ix (0 .. $#ccom) { | |
404 | if (!$opt{cplusplus} && $ccom[$ix] =~ s!^//!!) { | |
405 | $cppc++; | |
406 | $file{changes} += $c =~ s/$rccs$ix$rcce/$ccs$ccom[$ix] $cce/; | |
407 | } | |
408 | else { | |
409 | $c =~ s/$rccs$ix$rcce/$ccom[$ix]/; | |
410 | } | |
411 | } | |
412 | ||
413 | if ($cppc) { | |
414 | my $s = $cppc != 1 ? 's' : ''; | |
415 | warning("Uses $cppc C++ style comment$s, which is not portable"); | |
416 | } | |
417 | ||
418 | if ($file{changes}) { | |
419 | if (exists $opt{copy}) { | |
420 | my $newfile = "$filename$opt{copy}"; | |
421 | if (-e $newfile) { | |
422 | error("'$newfile' already exists, refusing to write copy of '$filename'"); | |
423 | } | |
424 | else { | |
425 | local *F; | |
426 | if (open F, ">$newfile") { | |
427 | info("Writing copy of '$filename' with changes to '$newfile'"); | |
428 | print F $c; | |
429 | close F; | |
430 | } | |
431 | else { | |
432 | error("Cannot open '$newfile' for writing: $!"); | |
433 | } | |
434 | } | |
435 | } | |
436 | elsif (exists $opt{patch} || $opt{changes}) { | |
437 | if (exists $opt{patch}) { | |
438 | unless ($patch_opened) { | |
439 | if (open PATCH, ">$opt{patch}") { | |
440 | $patch_opened = 1; | |
441 | } | |
442 | else { | |
443 | error("Cannot open '$opt{patch}' for writing: $!"); | |
444 | delete $opt{patch}; | |
445 | $opt{changes} = 1; | |
446 | goto fallback; | |
447 | } | |
448 | } | |
449 | mydiff(\*PATCH, $filename, $c); | |
450 | } | |
451 | else { | |
452 | fallback: | |
453 | info("Suggested changes:"); | |
454 | mydiff(\*STDOUT, $filename, $c); | |
455 | } | |
456 | } | |
457 | else { | |
458 | my $s = $file{changes} == 1 ? '' : 's'; | |
459 | info("$file{changes} potentially required change$s detected"); | |
460 | } | |
461 | } | |
462 | else { | |
463 | info("Looks good"); | |
464 | } | |
465 | } | |
466 | ||
467 | close PATCH if $patch_opened; | |
468 | ||
469 | exit 0; | |
470 | ||
471 | ####################################################################### | |
472 | ||
473 | sub mydiff | |
474 | { | |
475 | local *F = shift; | |
476 | my($file, $str) = @_; | |
477 | my $diff; | |
478 | ||
479 | if (exists $opt{diff}) { | |
480 | $diff = run_diff($opt{diff}, $file, $str); | |
481 | } | |
482 | ||
483 | if (!defined $diff and can_use('Text::Diff')) { | |
484 | $diff = Text::Diff::diff($file, \$str, { STYLE => 'Unified' }); | |
485 | $diff = <<HEADER . $diff; | |
486 | --- $file | |
487 | +++ $file.patched | |
488 | HEADER | |
489 | } | |
490 | ||
491 | if (!defined $diff) { | |
492 | $diff = run_diff('diff -u', $file, $str); | |
493 | } | |
494 | ||
495 | if (!defined $diff) { | |
496 | $diff = run_diff('diff', $file, $str); | |
497 | } | |
498 | ||
499 | if (!defined $diff) { | |
500 | error("Cannot generate a diff. Please install Text::Diff or use --copy."); | |
501 | return; | |
502 | } | |
503 | ||
504 | print F $diff; | |
505 | ||
506 | } | |
507 | ||
508 | sub run_diff | |
509 | { | |
510 | my($prog, $file, $str) = @_; | |
511 | my $tmp = 'dppptemp'; | |
512 | my $suf = 'aaa'; | |
513 | my $diff = ''; | |
514 | local *F; | |
515 | ||
516 | while (-e "$tmp.$suf") { $suf++ } | |
517 | $tmp = "$tmp.$suf"; | |
518 | ||
519 | if (open F, ">$tmp") { | |
520 | print F $str; | |
521 | close F; | |
522 | ||
523 | if (open F, "$prog $file $tmp |") { | |
524 | while (<F>) { | |
525 | s/\Q$tmp\E/$file.patched/; | |
526 | $diff .= $_; | |
527 | } | |
528 | close F; | |
529 | unlink $tmp; | |
530 | return $diff; | |
531 | } | |
532 | ||
533 | unlink $tmp; | |
534 | } | |
535 | else { | |
536 | error("Cannot open '$tmp' for writing: $!"); | |
537 | } | |
538 | ||
539 | return undef; | |
540 | } | |
541 | ||
542 | sub can_use | |
543 | { | |
544 | eval "use @_;"; | |
545 | return $@ eq ''; | |
546 | } | |
547 | ||
548 | sub rec_depend | |
549 | { | |
550 | my $func = shift; | |
551 | return () unless exists $depends{$func}; | |
552 | map { ($_, rec_depend($_)) } @{$depends{$func}}; | |
553 | } | |
554 | ||
555 | sub parse_version | |
556 | { | |
557 | my $ver = shift; | |
558 | ||
559 | if ($ver =~ /^(\d+)\.(\d+)\.(\d+)$/) { | |
560 | return ($1, $2, $3); | |
561 | } | |
562 | elsif ($ver !~ /^\d+\.[\d_]+$/) { | |
563 | die "cannot parse version '$ver'\n"; | |
564 | } | |
565 | ||
566 | $ver =~ s/_//g; | |
567 | $ver =~ s/$/000000/; | |
568 | ||
569 | my($r,$v,$s) = $ver =~ /(\d+)\.(\d{3})(\d{3})/; | |
570 | ||
571 | $v = int $v; | |
572 | $s = int $s; | |
573 | ||
574 | if ($r < 5 || ($r == 5 && $v < 6)) { | |
575 | if ($s % 10) { | |
576 | die "cannot parse version '$ver'\n"; | |
577 | } | |
578 | } | |
579 | ||
580 | return ($r, $v, $s); | |
581 | } | |
582 | ||
583 | sub format_version | |
584 | { | |
585 | my $ver = shift; | |
586 | ||
587 | $ver =~ s/$/000000/; | |
588 | my($r,$v,$s) = $ver =~ /(\d+)\.(\d{3})(\d{3})/; | |
589 | ||
590 | $v = int $v; | |
591 | $s = int $s; | |
592 | ||
593 | if ($r < 5 || ($r == 5 && $v < 6)) { | |
594 | if ($s % 10) { | |
595 | die "invalid version '$ver'\n"; | |
596 | } | |
597 | $s /= 10; | |
598 | ||
599 | $ver = sprintf "%d.%03d", $r, $v; | |
600 | $s > 0 and $ver .= sprintf "_%02d", $s; | |
601 | ||
602 | return $ver; | |
603 | } | |
604 | ||
605 | return sprintf "%d.%d.%d", $r, $v, $s; | |
606 | } | |
607 | ||
608 | sub info | |
609 | { | |
610 | $opt{quiet} and return; | |
611 | print @_, "\n"; | |
612 | } | |
613 | ||
614 | sub diag | |
615 | { | |
616 | $opt{quiet} and return; | |
617 | $opt{diag} and print @_, "\n"; | |
618 | } | |
619 | ||
620 | sub warning | |
621 | { | |
622 | $opt{quiet} and return; | |
623 | print "*** ", @_, "\n"; | |
624 | } | |
625 | ||
626 | sub error | |
627 | { | |
628 | print "*** ERROR: ", @_, "\n"; | |
629 | } | |
630 | ||
631 | my %given_hints; | |
632 | sub hint | |
633 | { | |
634 | $opt{quiet} and return; | |
635 | $opt{hints} or return; | |
636 | my $func = shift; | |
637 | exists $hints{$func} or return; | |
638 | $given_hints{$func}++ and return; | |
639 | my $hint = $hints{$func}; | |
640 | $hint =~ s/^/ /mg; | |
641 | print " --- hint for $func ---\n", $hint; | |
642 | } | |
643 | ||
644 | sub usage | |
645 | { | |
646 | my($usage) = do { local(@ARGV,$/)=($0); <> } =~ /^=head\d$HS+SYNOPSIS\s*^(.*?)\s*^=/ms; | |
647 | my %M = ( 'I' => '*' ); | |
648 | $usage =~ s/^\s*perl\s+\S+/$^X $0/; | |
649 | $usage =~ s/([A-Z])<([^>]+)>/$M{$1}$2$M{$1}/g; | |
650 | ||
651 | print <<ENDUSAGE; | |
652 | ||
653 | Usage: $usage | |
654 | ||
655 | See perldoc $0 for details. | |
656 | ||
657 | ENDUSAGE | |
658 | ||
659 | exit 2; | |
660 | } |