change versioning...
[spider.git] / perl / DXUtil.pm
1 #
2 # various utilities which are exported globally
3 #
4 # Copyright (c) 1998 - Dirk Koopman G1TLH
5 #
6 # $Id$
7 #
8
9 package DXUtil;
10
11 use Date::Parse;
12 use IO::File;
13 use File::Copy;
14 use Data::Dumper;
15
16 use strict;
17
18 use vars qw($VERSION $BRANCH);
19 use vars qw(@month %patmap @ISA @EXPORT);
20
21 require Exporter;
22 @ISA = qw(Exporter);
23 @EXPORT = qw(atime ztime cldate cldatetime slat slong yesno promptf 
24                          parray parraypairs phex shellregex readfilestr writefilestr
25                          filecopy ptimelist
26              print_all_fields cltounix unpad is_callsign is_latlong
27                          is_qra is_freq is_digits is_pctext is_pcflag insertitem deleteitem
28                          is_prefix dd dxver
29             );
30
31
32 @month = qw(Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec);
33 %patmap = (
34                    '*' => '.*',
35                    '?' => '.',
36                    '[' => '[',
37                    ']' => ']'
38 );
39
40 # a full time for logging and other purposes
41 sub atime
42 {
43         my $t = shift;
44         my ($sec,$min,$hour,$mday,$mon,$year) = gmtime((defined $t) ? $t : time);
45         $year += 1900;
46         my $buf = sprintf "%02d%s%04d\@%02d:%02d:%02d", $mday, $month[$mon], $year, $hour, $min, $sec;
47         return $buf;
48 }
49
50 # get a zulu time in cluster format (2300Z)
51 sub ztime
52 {
53         my $t = shift;
54         $t = defined $t ? $t : time;
55         my $dst = shift;
56         my ($sec,$min,$hour) = $dst ? localtime($t): gmtime($t);
57         my $buf = sprintf "%02d%02d%s", $hour, $min, ($dst) ? '' : 'Z';
58         return $buf;
59 }
60
61 # get a cluster format date (23-Jun-1998)
62 sub cldate
63 {
64         my $t = shift;
65         $t = defined $t ? $t : time;
66         my $dst = shift;
67         my ($sec,$min,$hour,$mday,$mon,$year) = $dst ? localtime($t) : gmtime($t);
68         $year += 1900;
69         my $buf = sprintf "%2d-%s-%04d", $mday, $month[$mon], $year;
70         return $buf;
71 }
72
73 # return a cluster style date time
74 sub cldatetime
75 {
76         my $t = shift;
77         my $dst = shift;
78         my $date = cldate($t, $dst);
79         my $time = ztime($t, $dst);
80         return "$date $time";
81 }
82
83 # return a unix date from a cluster date and time
84 sub cltounix
85 {
86         my $date = shift;
87         my $time = shift;
88         my ($thisyear) = (gmtime)[5] + 1900;
89
90         return 0 unless $date =~ /^\s*(\d+)-(\w\w\w)-([12][90]\d\d)$/;
91         return 0 if $3 > 2036;
92         return 0 unless abs($thisyear-$3) <= 1;
93         $date = "$1 $2 $3";
94         return 0 unless $time =~ /^([012]\d)([012345]\d)Z$/;
95         $time = "$1:$2 +0000";
96         my $r = str2time("$date $time");
97         return $r unless $r;
98         return $r == -1 ? undef : $r;
99 }
100
101 # turn a latitude in degrees into a string
102 sub slat
103 {
104         my $n = shift;
105         my ($deg, $min, $let);
106         $let = $n >= 0 ? 'N' : 'S';
107         $n = abs $n;
108         $deg = int $n;
109         $min = int ((($n - $deg) * 60) + 0.5);
110         return "$deg $min $let";
111 }
112
113 # turn a longitude in degrees into a string
114 sub slong
115 {
116         my $n = shift;
117         my ($deg, $min, $let);
118         $let = $n >= 0 ? 'E' : 'W';
119         $n = abs $n;
120         $deg = int $n;
121         $min = int ((($n - $deg) * 60) + 0.5);
122         return "$deg $min $let";
123 }
124
125 # turn a true into 'yes' and false into 'no'
126 sub yesno
127 {
128         my $n = shift;
129         return $n ? $main::yes : $main::no;
130 }
131
132 # provide a data dumpered version of the object passed
133 sub dd
134 {
135         my $value = shift;
136         my $dd = new Data::Dumper([$value]);
137         $dd->Indent(0);
138         $dd->Terse(1);
139     $dd->Quotekeys($] < 5.005 ? 1 : 0);
140         $value = $dd->Dumpxs;
141         $value =~ s/([\r\n\t])/sprintf("%%%02X", ord($1))/eg;
142         $value =~ s/^\s*\[//;
143     $value =~ s/\]\s*$//;
144         
145         return $value;
146 }
147
148 # format a prompt with its current value and return it with its privilege
149 sub promptf
150 {
151         my ($line, $value) = @_;
152         my ($priv, $prompt, $action) = split ',', $line;
153
154         # if there is an action treat it as a subroutine and replace $value
155         if ($action) {
156                 my $q = qq{\$value = $action(\$value)};
157                 eval $q;
158         } elsif (ref $value) {
159                 $value = dd($value);
160         }
161         $prompt = sprintf "%15s: %s", $prompt, $value;
162         return ($priv, $prompt);
163 }
164
165 # turn a hex field into printed hex
166 sub phex
167 {
168         my $val = shift;
169         return sprintf '%X', $val;
170 }
171
172 # take an arg as a hash of call=>time pairs and print it
173 sub ptimelist
174 {
175         my $ref = shift;
176         my $out;
177         for (sort keys %$ref) {
178                 $out .= "$_=$ref->{$_}, ";
179         }
180         chop $out;
181         chop $out;
182         return $out;    
183 }
184
185 # take an arg as an array list and print it
186 sub parray
187 {
188         my $ref = shift;
189         return ref $ref ? join(', ', @{$ref}) : $ref;
190 }
191
192 # take the arg as an array reference and print as a list of pairs
193 sub parraypairs
194 {
195         my $ref = shift;
196         my $i;
197         my $out;
198   
199         for ($i = 0; $i < @$ref; $i += 2) {
200                 my $r1 = @$ref[$i];
201                 my $r2 = @$ref[$i+1];
202                 $out .= "$r1-$r2, ";
203         }
204         chop $out;                                      # remove last space
205         chop $out;                                      # remove last comma
206         return $out;
207 }
208
209 sub _sort_fields
210 {
211         my $ref = shift;
212         my @a = split /,/, $ref->field_prompt(shift); 
213         my @b = split /,/, $ref->field_prompt(shift); 
214         return lc $a[1] cmp lc $b[1];
215 }
216
217 # print all the fields for a record according to privilege
218 #
219 # The prompt record is of the format '<priv>,<prompt>[,<action>'
220 # and is expanded by promptf above
221 #
222 sub print_all_fields
223 {
224         my $self = shift;                       # is a dxchan
225         my $ref = shift;                        # is a thingy with field_prompt and fields methods defined
226         my @out;
227         my @fields = $ref->fields;
228         my $field;
229         my $width = $self->width - 1;
230         $width ||= 80;
231
232         foreach $field (sort {_sort_fields($ref, $a, $b)} @fields) {
233                 if (defined $ref->{$field}) {
234                         my ($priv, $ans) = promptf($ref->field_prompt($field), $ref->{$field});
235                         my @tmp;
236                         if (length $ans > $width) {
237                                 my ($p, $a) = split /: /, $ans, 2;
238                                 my $l = (length $p) + 2;
239                                 my $al = ($width - 1) - $l;
240                                 my $bit;
241                                 while (length $a > $al ) {
242                                         ($bit, $a) = unpack "A$al A*", $a;
243                                         push @tmp, "$p: $bit";
244                                         $p = ' ' x ($l - 2);
245                                 }
246                                 push @tmp, "$p: $a" if length $a;
247                         } else {
248                                 push @tmp, $ans;
249                         }
250                         push @out, @tmp if ($self->priv >= $priv);
251                 }
252         }
253         return @out;
254 }
255
256 # generate a regex from a shell type expression 
257 # see 'perl cookbook' 6.9
258 sub shellregex
259 {
260         my $in = shift;
261         $in =~ s{(.)} { $patmap{$1} || "\Q$1" }ge;
262         return '^' . $in . "\$";
263 }
264
265 # read in a file into a string and return it. 
266 # the filename can be split into a dir and file and the 
267 # file can be in upper or lower case.
268 # there can also be a suffix
269 sub readfilestr
270 {
271         my ($dir, $file, $suffix) = @_;
272         my $fn;
273         my $f;
274         if ($suffix) {
275                 $f = uc $file;
276                 $fn = "$dir/$f.$suffix";
277                 unless (-e $fn) {
278                         $f = lc $file;
279                         $fn = "$dir/$file.$suffix";
280                 }
281         } elsif ($file) {
282                 $f = uc $file;
283                 $fn = "$dir/$file";
284                 unless (-e $fn) {
285                         $f = lc $file;
286                         $fn = "$dir/$file";
287                 }
288         } else {
289                 $fn = $dir;
290         }
291
292         my $fh = new IO::File $fn;
293         my $s = undef;
294         if ($fh) {
295                 local $/ = undef;
296                 $s = <$fh>;
297                 $fh->close;
298         }
299         return $s;
300 }
301
302 # write out a file in the format required for reading
303 # in via readfilestr, it expects the same arguments 
304 # and a reference to an object
305 sub writefilestr
306 {
307         my $dir = shift;
308         my $file = shift;
309         my $suffix = shift;
310         my $obj = shift;
311         my $fn;
312         my $f;
313         
314         confess('no object to write in writefilestr') unless $obj;
315         confess('object not a reference in writefilestr') unless ref $obj;
316         
317         if ($suffix) {
318                 $f = uc $file;
319                 $fn = "$dir/$f.$suffix";
320                 unless (-e $fn) {
321                         $f = lc $file;
322                         $fn = "$dir/$file.$suffix";
323                 }
324         } elsif ($file) {
325                 $f = uc $file;
326                 $fn = "$dir/$file";
327                 unless (-e $fn) {
328                         $f = lc $file;
329                         $fn = "$dir/$file";
330                 }
331         } else {
332                 $fn = $dir;
333         }
334
335         my $fh = new IO::File ">$fn";
336         if ($fh) {
337                 my $dd = new Data::Dumper([ $obj ]);
338                 $dd->Indent(1);
339                 $dd->Terse(1);
340                 $dd->Quotekeys(0);
341                 #       $fh->print(@_) if @_ > 0;     # any header comments, lines etc
342                 $fh->print($dd->Dumpxs);
343                 $fh->close;
344         }
345 }
346
347 sub filecopy
348 {
349         copy(@_) or return $!;
350 }
351
352 # remove leading and trailing spaces from an input string
353 sub unpad
354 {
355         my $s = shift;
356         $s =~ s/\s+$//;
357         $s =~ s/^\s+//;
358         return $s;
359 }
360
361 # check that a field only has callsign characters in it
362 sub is_callsign
363 {
364         return $_[0] =~ m!^(?:[A-Z]{1,2}\d+ | \d[A-Z]{1,2}\d+)        # basic prefix
365                        (?:/(?:[A-Z]{1,2}\d+ | \d[A-Z]{1,2}\d+))?  # / another one (possibly)
366                                            [A-Z]{1,3}                                 # callsign letters
367                                            (?:/(?:[A-Z]{1,2}\d+ | \d[A-Z]{1,2}\d+))?  # / another prefix possibly
368                        (?:/[0-9A-Z]{1,2})?                        # /0-9A-Z+ possibly
369                                            (?:-\d{1,2})?                              # - nn possibly
370                                          $!x;
371 }
372
373 sub is_prefix
374 {
375         return $_[0] =~ m!^(?:[A-Z]{1,2}\d+ | \d[A-Z]{1,2}\d+)!x        # basic prefix
376 }
377         
378
379 # check that a PC protocol field is valid text
380 sub is_pctext
381 {
382         return undef unless length $_[0];
383         return undef if $_[0] =~ /[\x00-\x08\x0a-\x1f\x80-\x9f]/;
384         return 1;
385 }
386
387 # check that a PC prot flag is fairly valid (doesn't check the difference between 1/0 and */-)
388 sub is_pcflag
389 {
390         return $_[0] =~ /^[01\*\-]+$/;
391 }
392
393 # check that a thing is a frequency
394 sub is_freq
395 {
396         return $_[0] =~ /^\d+(?:\.\d+)?$/;
397 }
398
399 # check that a thing is just digits
400 sub is_digits
401 {
402         return $_[0] =~ /^[\d]+$/;
403 }
404
405 # does it look like a qra locator?
406 sub is_qra
407 {
408         return $_[0] =~ /^[A-Ra-r][A-Ra-r]\d\d[A-Xa-x][A-Xa-x]$/;
409 }
410
411 # does it look like a valid lat/long
412 sub is_latlong
413 {
414         return $_[0] =~ /^\s*\d{1,2}\s+\d{1,2}\s*[NnSs]\s+1?\d{1,2}\s+\d{1,2}\s*[EeWw]\s*$/;
415 }
416
417 # insert an item into a list if it isn't already there returns 1 if there 0 if not
418 sub insertitem
419 {
420         my $list = shift;
421         my $item = shift;
422         
423         return 1 if grep {$_ eq $item } @$list;
424         push @$list, $item;
425         return 0;
426 }
427
428 # delete an item from a list if it is there returns no deleted 
429 sub deleteitem
430 {
431         my $list = shift;
432         my $item = shift;
433         my $n = @$list;
434         
435         @$list = grep {$_ ne $item } @$list;
436         return $n - @$list;
437 }
438
439 sub dxver
440 {
441         my $s = shift;
442         my ($a, $b, $c, $d) = $s =~ /(\d+)\.(\d+)\.(?:(\d+)\.(\d+))?/;
443         
444         my $v = sprintf( "%d.%03d", $a, $b) || 0;
445         my $br = sprintf( "%d.%03d", $c, $d) if defined $c;
446         $br ||= 0;
447
448         $main::build += $v;
449         $main::branch += $br;
450         return ($v, $br);
451 }
452
453 INIT {
454         ($VERSION, $BRANCH) = dxver(q$Revision$);
455 }