File Coverage

blib/lib/FTN/Addr.pm
Criterion Covered Total %
statement 108 112 96.4
branch 74 126 58.7
condition 41 110 37.2
subroutine 33 33 100.0
pod 9 19 47.3
total 265 400 66.2


line stmt bran cond sub pod time code
1             package FTN::Addr;
2             $FTN::Addr::VERSION = '20160303';
3              
4 6     6   87373 use strict;
  6         9  
  6         176  
5 6     6   21 use warnings;
  6         9  
  6         136  
6              
7 6     6   20 use Carp ();
  6         10  
  6         60  
8 6     6   21 use Scalar::Util ();
  6         6  
  6         267  
9              
10             =head1 NAME
11              
12             FTN::Addr - Object-oriented module for creation and working with FTN addresses.
13              
14             =head1 VERSION
15              
16             version 20160303
17              
18             =head1 SYNOPSIS
19              
20             use FTN::Addr;
21              
22             my $a = FTN::Addr -> new( '1:23/45' ) or die "this is not a correct address";
23              
24             my $b = FTN::Addr -> new( '1:23/45@fidonet' ) or die 'cannot create address';
25              
26             print "Hey! They are the same!\n" if $a eq $b; # they actually are, because default domain is 'fidonet'
27              
28             $b -> set_domain( 'othernet' );
29              
30             print "Hey! They are the same!\n" if $a eq $b; # no output as we changed domain
31              
32             $b = FTN::Addr -> new( '44.22', $a ) or die "cannot create address"; # takes the rest of information from optional $a
33              
34             $b = $a -> new( '44.22' ) or die "cannot create address"; # the same
35              
36             print $a -> f4, "\n"; # 1:23/45.0
37              
38             print $a -> s4, "\n"; # 1:23/45
39              
40             print $a -> f5, "\n"; # 1:23/45.0@fidonet
41              
42             print $a -> s5, "\n"; # 1:23/45@fidonet
43              
44             =head1 DESCRIPTION
45              
46             FTN::Addr module is for creation and working with FTN addresses. Supports domains, different representations and comparison operators.
47              
48             =cut
49              
50             use overload
51 6         41 eq => \ &_eq,
52             cmp => \ &_cmp,
53 6     6   6364 fallback => 1;
  6         5049  
54              
55 6     6   409 use constant DEFAULT_DOMAIN => 'fidonet';
  6         9  
  6         10600  
56              
57             my $domain_re = '[a-z\d_~-]{1,8}';
58             # frl-1028.002:
59             # The Domain Name
60             # ---------------
61              
62             # The domain name MUST be a character string not more than 8
63             # characters long and MUST include only characters as defined below in
64             # BNF. Any other character cannot be used in a domain name.
65              
66             # domain = *pchar
67             # pchar = alphaLC | digit | safe
68             # alphaLC = "a" | "b" | ... | "z"
69             # digit = "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9"
70             # safe = '-' | '_' | '~'
71              
72              
73             sub _remove_presentations {
74 12     12   9 my $t = shift;
75              
76 12         27 delete @$t{ qw/ full4d full5d short4d short5d fqfa brake_style / };
77             }
78              
79             =head1 OBJECT CREATION
80              
81             =head2 new
82              
83             Can be called as class or object method:
84              
85             my $t = FTN::Addr -> new( '1:23/45' ) or die 'something wrong!';
86              
87             $t = $t -> new( '1:22/33.44@fidonet' ) or die 'something wrong!'; # advisable to use class call here instead:
88             $t = FTN::Addr -> new( '1:22/33.44@fidonet' ) or die 'something wrong!';
89              
90             Default domain is 'fidonet'. If point isn't specified, it's considered to be 0.
91              
92             Address can be:
93              
94             3d/4d 1:23/45 or 1:23/45.0
95             5d 1:23/45@fidonet or 1:23/45.0@fidonet
96             fqfa fidonet#1:23/45.0
97             The Brake! FTN-compatible mailer for OS/2 style fidonet.1.23.45.0
98              
99             If passed address misses any part except point and domain, the base is needed to get the missing information from (including domain). It can be optional second parameter (already created FTN::Addr object) in case of class method call or object itself in case of object method call.
100              
101             my $an = FTN::Addr -> new( '99', $t ); # class call. address in $an is 1:22/99.0@fidonet
102             $an = $t -> new( '99' ); # object call. the same resulting address.
103              
104             Performs field validation.
105              
106             In case of error returns undef in scalar context or empty list in list context.
107              
108             =cut
109              
110             sub new {
111 35     35 1 8657 my $either = shift;
112 35   66     106 my $class = ref( $either ) || $either;
113 35         30 my $addr = shift;
114              
115             return
116 35 50       52 unless defined $addr;
117              
118 35         30 my %new;
119              
120 35 50       1068 if ( $addr =~ m!^($domain_re)\.(\d+)\.(\d+)\.(-?\d+)\.(-?\d+)$! ) { # fidonet.2.451.31.0
    50          
    100          
121 0         0 @new{ qw/ domain
122             zone
123             net
124             node
125             point
126             /
127             } = ( $1, $2, $3, $4, $5 );
128             } elsif ( $addr =~ m!^($domain_re)#(\d+):(\d+)/(-?\d+)\.(-?\d+)$! ) { # fidonet#2:451/31.0
129 0         0 @new{ qw/ domain
130             zone
131             net
132             node
133             point
134             /
135             } = ( $1, $2, $3, $4, $5 );
136             } elsif ( $addr =~ m!^(\d+):(\d+)/(-?\d+)(?:\.(-?\d+))?(?:@($domain_re))?$! ) { # 2:451/31.0@fidonet 2:451/31@fidonet 2:451/31.0 2:451/31
137 21   100     215 @new{ qw/ domain
      100        
138             zone
139             net
140             node
141             point
142             /
143             } = ( $5 || DEFAULT_DOMAIN,
144             $1, $2, $3,
145             $4 || 0,
146             );
147             } else { # partials. need base. 451/31.0 451/31 31.1 31 .1
148 14 100       24 my $base = ref $either ? $either : shift;
149              
150             return
151 14 50 33     155 unless $base
      33        
      33        
152             && ref $base
153             && Scalar::Util::blessed $base
154             && $base -> isa( 'FTN::Addr' );
155              
156 14 100       76 if ( $addr =~ m!^(\d+)/(-?\d+)(?:\.(-?\d+))?$! ) { # 451/31.0 451/31
    100          
    50          
157 4   50     6 @new{ qw/ domain
158             zone
159             net
160             node
161             point
162             /
163             } = ( $base -> domain,
164             $base -> zone,
165             $1,
166             $2,
167             $3 || 0,
168             );
169             } elsif ( $addr =~ m!^(-?\d+)(?:\.(-?\d+))?$! ) { # 31.1 31
170 9   100     14 @new{ qw/ domain
171             zone
172             net
173             node
174             point
175             /
176             } = ( $base -> domain,
177             $base -> zone,
178             $base -> net,
179             $1,
180             $2 || 0,
181             );
182             } elsif ( $addr =~ m!^\.(-?\d+)$! ) { # .1
183 1         4 @new{ qw/ domain
184             zone
185             net
186             node
187             point
188             /
189             } = ( $base -> domain,
190             $base -> zone,
191             $base -> net,
192             $base -> node,
193             $1,
194             );
195             } else { # not recognizable
196 0         0 return;
197             }
198             }
199              
200             return
201             unless _validate_domain( $new{domain} )
202             && _validate_zone( $new{zone} )
203             && _validate_net( $new{net} )
204             && _validate_node( $new{node} )
205             && _validate_point( $new{point} )
206             && ( $new{node} != -1 # node application
207             || $new{point} == 0
208             )
209             && ( $new{node} > 0 # point application
210 35 50 33     90 || $new{point} != -1
      33        
      33        
      33        
      33        
      33        
      33        
      33        
211             );
212              
213 35         118 bless \ %new, $class;
214             }
215              
216             sub _validate_domain {
217 38 50   38   406 defined $_[ 0 ]
218             # && length( $_[ 0 ] )
219             # && length( $_[ 0 ] ) <= 8 # FRL-1002.001
220             # && index( $_[ 0 ], '.' ) == -1; # FRL-1002.001
221             && $_[ 0 ] =~ m/^$domain_re$/; # frl-1028.002
222             }
223              
224             sub _validate_zone {
225 37 50 33 37   264 defined $_[ 0 ]
226             && 1 <= $_[ 0 ] && $_[ 0 ] <= 32767; # FRL-1002.001, frl-1028.002
227             }
228              
229             sub _validate_net {
230 37 50 33 37   247 defined $_[ 0 ]
231             && 1 <= $_[ 0 ] && $_[ 0 ] <= 32767; # FRL-1002.001, frl-1028.002
232             }
233              
234             sub _validate_node {
235 37 50 33 37   235 defined $_[ 0 ]
236             && -1 <= $_[ 0 ] && $_[ 0 ] <= 32767; # FRL-1002.001, frl-1028.002
237             }
238              
239             sub _validate_point {
240 38 50 33 38   621 defined $_[ 0 ]
241             # && 0 <= $_[ 0 ] && $_[ 0 ] <= 32767; # FRL-1002.001: 0 .. 32765
242             && -1 <= $_[ 0 ] && $_[ 0 ] <= 32767; # frl-1028.002: -1 .. 32767
243             }
244              
245             =head2 clone
246              
247             $th = $an -> clone
248              
249             =cut
250              
251             sub clone {
252 1 50   1 1 5 ref( my $inst = shift ) or Carp::croak "I'm only an object method!";
253              
254 1         5 bless { %$inst }, ref $inst;
255             }
256              
257             =head1 FIELD ACCESS
258              
259             Direct access to object fields. Checking is performed (dies on error). Setters return itself (for possible chaining).
260              
261             =head2 domain:
262              
263             $an -> set_domain( 'mynet' );
264             $an -> domain;
265             $an -> domain( 'leftnet' );
266              
267             =for Pod::Coverage domain set_domain
268              
269             =cut
270              
271             sub domain {
272 61 50   61 0 12371 ref( my $inst = shift ) or Carp::croak "I'm only an object method!";
273              
274             @_ ?
275             $inst -> set_domain( @_ )
276 61 100       288 : $inst -> {domain};
277             }
278              
279             sub set_domain {
280 3 50   3 0 231 ref( my $inst = shift ) or Carp::croak "I'm only an object method!";
281              
282 3         3 my $value = shift;
283              
284 3 0       7 die 'incorrect domain: ' . ( defined $value ? $value : 'undef' )
    50          
285             unless _validate_domain( $value );
286              
287 3         7 $inst -> {domain} = $value;
288 3         6 $inst -> _remove_presentations;
289              
290 3         4 $inst;
291             }
292              
293             =head2 zone:
294              
295             $an -> set_zone( 2 );
296             $an -> zone;
297             $an -> zone( 3 );
298              
299             =for Pod::Coverage zone set_zone
300              
301             =cut
302              
303             sub zone {
304 59 50   59 0 337 ref( my $inst = shift ) or Carp::croak "I'm only an object method!";
305              
306             @_ ?
307             $inst -> set_zone( @_ )
308 59 100       248 : $inst -> {zone};
309             }
310              
311             sub set_zone {
312 2 50   2 0 216 ref( my $inst = shift ) or Carp::croak "I'm only an object method!";
313              
314 2         2 my $value = shift;
315              
316 2 0       4 die 'incorrect zone: ' . ( defined $value ? $value : 'undef' )
    50          
317             unless _validate_zone( $value );
318              
319 2         4 $inst -> {zone} = $value;
320 2         6 $inst -> _remove_presentations;
321              
322 2         2 $inst;
323             }
324              
325             =head2 net:
326              
327             $an -> set_net( 456 );
328             $an -> net;
329             $an -> net( 5020 );
330              
331             =for Pod::Coverage net set_net
332              
333             =cut
334              
335             sub net {
336 56 50   56 0 578 ref( my $inst = shift ) or Carp::croak "I'm only an object method!";
337              
338             @_ ?
339             $inst -> set_net( @_ )
340 56 100       266 : $inst -> {net};
341             }
342              
343             sub set_net {
344 2 50   2 0 5 ref( my $inst = shift ) or Carp::croak "I'm only object method!";
345              
346 2         3 my $value = shift;
347              
348 2 0       3 die 'incorrect net: ' . ( defined $value ? $value : 'undef' )
    50          
349             unless _validate_net( $value );
350              
351 2         3 $inst -> {net} = $value;
352 2         4 $inst -> _remove_presentations;
353              
354 2         2 $inst;
355             }
356              
357             =head2 node:
358              
359             $an -> set_node( 33 );
360             $an -> node;
361             $an -> node( 60 );
362              
363             =for Pod::Coverage node set_node
364              
365             =cut
366              
367             sub node {
368 47 50   47 0 532 ref( my $inst = shift ) or Carp::croak "I'm only an object method!";
369              
370             @_ ?
371             $inst -> set_node( @_ )
372 47 100       207 : $inst -> {node};
373             }
374              
375             sub set_node {
376 2 50   2 0 6 ref(my $inst = shift) or Carp::croak "I'm only object method!";
377              
378 2         14 my $value = shift;
379              
380 2 0       3 die 'incorrect node: ' . ( defined $value ? $value : 'undef' )
    50          
381             unless _validate_node( $value );
382              
383 2         3 $inst -> {node} = $value;
384 2         4 $inst -> _remove_presentations;
385              
386 2         4 $inst;
387             }
388              
389             =head2 point:
390              
391             $an -> set_point( 6 );
392             $an -> point;
393             $an -> point( 0 );
394              
395             =for Pod::Coverage point set_point
396              
397             =cut
398              
399             sub point {
400 46 50   46 0 543 ref( my $inst = shift ) or Carp::croak "I'm only an object method!";
401              
402             @_ ?
403             $inst -> set_point( @_ )
404 46 100       190 : $inst -> {point};
405             }
406              
407             sub set_point {
408 3 50   3 0 10 ref(my $inst = shift) or Carp::croak "I'm only object method!";
409              
410 3         4 my $value = shift;
411              
412 3 0       7 die 'incorrect point: ' . ( defined $value ? $value : 'undef' )
    50          
413             unless _validate_point( $value );
414              
415 3         6 $inst -> {point} = $value;
416 3         8 $inst -> _remove_presentations;
417              
418 3         4 $inst;
419             }
420              
421             =head1 REPRESENTATION
422              
423             =head2 f4 - Full 4d address (without domain):
424              
425             print $an -> f4; # 1:22/99.0
426              
427             =cut
428              
429             sub f4 {
430 23 50   23 1 62 ref( my $inst = shift ) or Carp::croak "I'm only an object method!";
431              
432             $inst -> {full4d} = sprintf '%d:%d/%d.%d', map $inst -> { $_ }, qw/ zone net node point /
433 23 100       191 unless exists $inst -> {full4d};
434              
435 23         91 $inst -> {full4d};
436             }
437              
438             =head2 s4 - Short form (if possible) of 4d address:
439              
440             print $an -> s4; # 1:22/99
441              
442             =cut
443              
444             sub s4 {
445 23 50   23 1 62 ref( my $inst = shift ) or Carp::croak "I'm only an object method!";
446              
447             $inst -> {short4d} = sprintf '%d:%d/%d%s',
448             map( $inst -> { $_ }, qw/ zone net node / ),
449             $inst -> {point} ? '.' . $inst -> {point} : ''
450 23 100       200 unless exists $inst -> {short4d};
    100          
451              
452 23         96 $inst -> {short4d};
453             }
454              
455             =head2 f5 - Full 5d address (with domain):
456              
457             print $an -> f5; # 1:22/99.0@fidonet
458              
459             =cut
460              
461             sub f5 {
462 23 50   23 1 58 ref( my $inst = shift ) or Carp::croak "I'm only an object method!";
463              
464             $inst -> {full5d} = sprintf '%d:%d/%d.%d@%s', map $inst -> { $_ }, qw/ zone net node point domain /
465 23 100       193 unless exists $inst -> {full5d};
466              
467 23         84 $inst -> {full5d};
468             }
469              
470             =head2 s5 - Short form (if possible - only for nodes) of 5d address:
471              
472             print $an -> s5; # 1:22/99@fidonet
473              
474             =cut
475              
476             sub s5 {
477 23 50   23 1 99 ref( my $inst = shift ) or Carp::croak "I'm only an object method!";
478              
479             $inst -> {short5d} = sprintf '%d:%d/%d%s@%s',
480             map( $inst -> { $_ }, qw/ zone net node / ),
481             $inst -> {point} ? '.' . $inst -> {point} : '',
482             $inst -> {domain}
483 23 100       218 unless exists $inst -> {short5d};
    100          
484              
485 23         75 $inst -> {short5d};
486             }
487              
488             =head2 fqfa - Full qualified FTN address:
489              
490             print $an -> fqfa; # fidonet#1:22/99.0
491              
492             =cut
493              
494             sub fqfa {
495 4 50   4 1 574 ref( my $inst = shift ) or Carp::croak "I'm only an object method!";
496              
497             $inst -> {fqfa} = sprintf '%s#%d:%d/%d.%d', map $inst -> { $_ }, qw/ domain zone net node point /
498 4 100       26 unless exists $inst -> {fqfa};
499              
500 4         13 $inst -> {fqfa};
501             }
502              
503             =head2 bs - The Brake! FTN-compatible mailer for OS/2 style representation:
504              
505             print $an -> bs; # fidonet.1.22.99.0
506              
507             =cut
508              
509             sub bs {
510 12 50   12 1 32 ref( my $inst = shift ) or Carp::croak "I'm only an object method!";
511              
512             $inst -> {brake_style} = sprintf '%s.%d.%d.%d.%d', map $inst -> { $_ }, qw/ domain zone net node point /
513 12 100       97 unless exists $inst -> {brake_style};
514              
515 12         45 $inst -> {brake_style};
516             }
517              
518             =head1 COMPARISON
519              
520             =head2 equal, eq, cmp
521              
522             Two addresses can be compared.
523              
524             my $one = FTN::Addr -> new( '1:23/45.66@fidonet' ) or die "cannot create";
525              
526             my $two = FTN::Addr -> new( '1:23/45.66@fidonet' ) or die "cannot create";
527              
528             print "the same address!\n" if FTN::Addr -> equal( $one, $two ); # should print the message
529              
530             print "the same address!\n" if $one eq $two; # the same result
531              
532             print "but objects are different\n" if $one != $two; # should print the message
533              
534             The same way (comparison rules) as 'eq' works 'cmp' operator.
535              
536             =cut
537              
538             sub _eq { # eq operator
539             return
540 5 50 33 5   721 unless $_[ 1 ]
      33        
      33        
541             && ref $_[ 1 ]
542             && Scalar::Util::blessed $_[ 1 ]
543             && $_[ 1 ] -> isa( 'FTN::Addr' );
544              
545 5 50 33     12 $_[ 0 ] -> domain eq $_[ 1 ] -> domain
      33        
      33        
546             && $_[ 0 ] -> zone == $_[ 1 ] -> zone
547             && $_[ 0 ] -> net == $_[ 1 ] -> net
548             && $_[ 0 ] -> node == $_[ 1 ] -> node
549             && $_[ 0 ] -> point == $_[ 1 ] -> point;
550             }
551              
552             sub _cmp { # cmp operator
553             return
554 2 50 33 2   49 unless $_[ 1 ]
      33        
      33        
555             && ref $_[ 1 ]
556             && Scalar::Util::blessed $_[ 1 ]
557             && $_[ 1 ] -> isa( 'FTN::Addr' );
558              
559 2 50       6 if ( $_[ 2 ] ) { # arguments were swapped
560 0 0 0     0 $_[ 1 ] -> domain cmp $_[ 0 ] -> domain
      0        
      0        
561             || $_[ 1 ] -> zone <=> $_[ 0 ] -> zone
562             || $_[ 1 ] -> net <=> $_[ 0 ] -> net
563             || $_[ 1 ] -> node <=> $_[ 0 ] -> node
564             || $_[ 1 ] -> point <=> $_[ 0 ] -> point;
565             } else {
566 2 50 66     4 $_[ 0 ] -> domain cmp $_[ 1 ] -> domain
      66        
      33        
567             || $_[ 0 ] -> zone <=> $_[ 1 ] -> zone
568             || $_[ 0 ] -> net <=> $_[ 1 ] -> net
569             || $_[ 0 ] -> node <=> $_[ 1 ] -> node
570             || $_[ 0 ] -> point <=> $_[ 1 ] -> point;
571             }
572             }
573              
574             sub equal {
575 1 50   1 1 288 ref( my $class = shift ) and Carp::croak "I'm only a class method!";
576              
577             return
578 1 50 33     18 unless $_[ 0 ]
      33        
      33        
579             && ref $_[ 0 ]
580             && Scalar::Util::blessed $_[ 0 ]
581             && $_[ 0 ] -> isa( 'FTN::Addr' );
582              
583 1         4 _eq( @_ );
584             }
585              
586             =head1 AUTHOR
587              
588             Valery Kalesnik, C<< >>
589              
590             =head1 BUGS
591              
592             Please report any bugs or feature requests to C, or through
593             the web interface at L. I will be notified, and then you'll
594             automatically be notified of progress on your bug as I make changes.
595              
596             =head1 SUPPORT
597              
598             You can find documentation for this module with the perldoc command.
599              
600             perldoc FTN::Addr
601              
602             =cut
603              
604             1;